| Elektra 0.11.0

How to Write a Specification in Elektra for dockerd

# Overview

## Introduction

In this tutorial you will learn how to interactively use the `SpecElektra` specification language and `kdb` to write a configuration specification for dockerd.

## What you should already know

- Already know how to write a specification

## What you’ll learn

- how to create and mount a specification using `kdb`
- how to add keys with different types, defaults, enums, array specifications, wildcard specifications and examples to your specification and how to validate them

## What you'll do

- use `kdb` to create and mount a specification for dockerd
- define defaults, array / wildcard specifications, examples and checks for keys in the validation
- use the specification as a starting point for customizing the configuration of installed applications

## Scope

In this tutorial we will introduce a possible specification for dockerd. Using `kdb` we will configure the specification.

> ‍NOTE: As the specification for dockerd is quite big we will only present a sample for the above mentioned metakeys and link to a full specification dockerd-full-spec.

# Getting Started

Before we start just an overview of the structure:

- Specification file location: `/docker/daemon.json`
- Parent specification key: `spec:/sw/dockerd/dockerd/#0/current`

# Specification Types (values)

`Elektra` supports multiple types which leads to a more flexible specification. See type plugin for information about all the types that are supported.

# Mount Setup

We will be mounting an existing example dockerd-spec.

## Step 1: Mount dockerd specification

First you need to mount a specification file, in this case `dockerd.ini` to the `spec:/` namespace. You can define the path inside the `spec:/` namespace as `/sw/docker/dockerd/#0/current`, refer to the documentation to find out more about constructing the name.

sudo kdb mount "$PWD/examples/spec/dockerd.ini" spec:/sw/docker/dockerd/#0/current ni
# RET: 0

> ‍NOTE: If you encounter any error saying that you have already mounted some specification with the same name you can run `sudo kdb umount spec:/sw/docker/dockerd/#0/current` and rerun the above command.

> ‍Note: ni is the format which is used for the specification in the file. You can also choose to use json, then you need use yajl instead of ni.

## Step 2: Define a mountpoint

Next you can define, that this specification uses a specific mountpoint for a concrete application configuration. So you can say the concrete configuration should be written to `dockerd.ini`.

kdb meta-set spec:/sw/dockerd/dockerd/#0/current mountpoint /docker/daemon.json
# RET: 0

Your `dockerd.ini` file should now contain the `mountpoint` metakey:

> ‍NOTE: Excerpt of `cat $(kdb file spec:/sw/docker/dockerd/#0/current)`.

# ;Ni1
# ; Generated by the ni plugin using Elektra (see libelektra.org).
# =
# []
# meta:/mountpoint = /dockerd/daemon.json

## Step 3: Define <tt>json</tt> as plugin

Next we will define that our configuration should be written `json`.

We can do this by running:

kdb meta-set spec:/sw/dockerd/dockerd/#0/current infos/plugin "yajl"
# RET: 0

## Step 4: Do a specification mount

sudo kdb spec-mount "/sw/docker/dockerd/#0/current" ni
# RET: 0

This specification mount makes sure that the paths where the concrete configuration should be (`daemon.json`) are ready to fulfill our specification (`dockerd.ini`). Be aware that different files get mounted for different namespaces. You've a specification file (`dockerd.ini`) for the `spec`-namespace and three files (`daemon.json`) on different locations for the `dir`- `user`- and `system`-namespaces.

You can see the files by providing the namespace as prefix to the `kdb file` command (each shows a different path):

kdb file system:/sw/docker/dockerd/#0/current
# /dockerd/daemon.json
kdb file user:/sw/docker/dockerd/#0/current
# STDOUT-REGEX: /dockerd/daemon.json
kdb file dir:/sw/docker/dockerd/#0/current
# STDOUT-REGEX: /dockerd/daemon.json

> ‍NOTE: The $PWD should equal the PWD where you run `sudo kdb mount "$PWD/examples/spec/dockerd.ini" spec:/sw/docker/dockerd/#0/current ni`.

> ‍**_Note_**: The files only exist, when configuration values are stored there, i.e. they are created on the first `kdb set` and removed with the last `kdb rm`.

For more information about namespaces in Elektra please see here, a tutorial about the topic is available here.

# Writing specification for keys (manually)

> ‍NOTE: All output we display for `cat $(kdb file spec:/sw/docker/dockerd/#0/current)` is an excerpt of the whole file output.

In this example for `dockerd` we will be using 3 types of specifications:

- Simple specification (type and description)
- Enum specifications (for keys were only a set of possible options can be used)
- Array and wildcard specifications (for keys where a list of possible options can be used)

As the `dockerd` specification is big we will just present one of each of the above mentioned specification types.

> ‍NOTE: In Elektra we use `/` instead of `-` to seperate key names. This results in a hierarchical structure of key names. This commands will automatically store the specification in the `dockerd-daemon.ni` specification file.

## Array specification

[data/root]
meta:/type = string
meta:/description = Root directory of persistent Docker state
meta:/default = /var/lib/docker

In order to get the above specification we will need the following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/data/root type "string"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/data/root description "Root directory of persistent Docker state"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/data/root default "/var/lib/docker"
# RET: 0

In case no `data/root` key gets configured the value `/var/lib/docker` is used.

Let us verify that the metakeys have been set correctly:

> ‍NOTE: Excerpt of `cat $(kdb file spec:/sw/docker/dockerd/#0/current)`.

cat $(kdb file spec:/sw/docker/dockerd/#0/current)
# [data/root]
# meta:/type = string
# meta:/description = Root directory of persistent Docker state
# meta:/default = /var/lib/docker

## Enum specification (for keys were only a set of possible options can be used)

[default/cgroupns/mode]
meta:/description = Default mode for containers cgroup namespace
meta:/default = private
meta:/check/enum = #1
meta:/check/enum/#0 = host
meta:/check/enum/#1 = private

In order to get the above specification we will need the following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode description "Default mode for containers cgroup namespace"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode default "private"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode check/enum "#1"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode check/enum/#0 "host"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode check/enum/#1 "private"
# RET: 0

With this configuration we have managed to allow two possible values for `default/cgroupns/mode`. The values are `host` and `private`. The default value if we do not set any configuration is `private`.

Let us verify that the metakeys have been set correctly:

> ‍NOTE: Excerpt of `cat $(kdb file spec:/sw/docker/dockerd/#0/current)`.

cat $(kdb file spec:/sw/docker/dockerd/#0/current)
# [default/cgroupns/mode]
# meta:/check/enum/#0 = host
# meta:/check/enum/#1 = private
# meta:/description = Default mode for containers cgroup namespace
# meta:/default = private
# meta:/check/enum = #1
# [data/root]
# meta:/type = string
# meta:/description = Root directory of persistent Docker state
# meta:/default = /var/lib/docker

## Wildcard specifications (for keys where a list of possible options can be used)

[default/ulimits/_]
meta:/type = long
meta:/description = Default ulimits for containers
meta:/example = 64000

For this specification we want to allow an arbitrary number of `default ulimits`. The name of the `ulimits` does not matter but all should have the same metakeys.

In order to get the above specification we will need following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/default/ulimits/_ type "long"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/ulimits/_ description "Default ulimits for containers"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/ulimits/_ example "64000"
# RET: 0

The above specification will allow us to create any name below the `default/ulimits` key. The value needs to be a string. The sample value is `64000`.

Let us verify that the metakeys have been set correctly:

> ‍NOTE: Excerpt of `cat $(kdb file spec:/sw/docker/dockerd/#0/current)`.

cat $(kdb file spec:/sw/docker/dockerd/#0/current)
# [default/ulimits/_]
# meta:/type = long
# meta:/example = 64000
# meta:/description = Default ulimits for containers
# [default/cgroupns/mode]
# meta:/check/enum/#0 = host
# meta:/check/enum/#1 = private
# meta:/description = Default mode for containers cgroup namespace
# meta:/default = private
# meta:/check/enum = #1
# [data/root]
# meta:/type = string
# meta:/description = Root directory of persistent Docker state
# meta:/default = /var/lib/docker

## Array specifications (for keys where a list of possible options can be used)

[default/address/pools]
meta:/array/min = 0
meta:/description = Default address pools for node specific local networks (list)
[default/address/pools/#/base]
meta:/type = string
meta:/description = Ip address (ipv4) + subnet
meta:/example = 172.30.0.0/16
[default/address/pools/#/size]
meta:/type = short
meta:/description = Number of ip addresses in this pool with base
meta:/example = 24

The specification above shows the use of an array specification with the `#` character. We define the array to have a minimum value of `0` and arbitrary max length. We use `type`, `description` and `example` as metakeys on the keys beneath each array element.

This configuration above assures that we can configure `pools` with `base` and `size`.

It prevents a configuration like:

default/address/pools/#0/size = "test"

It will fail as `default/address/pools/#/size` is required to be of type `short` when set.

In order to get the above specification we will need following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools array/min "0"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools description "Default address pools for node specific local networks (list)"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/base type "string"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/base description "Ip address (ipv4) + subnet"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/base example "172.30.0.0/16"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/size type "short"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/size description "Number of ip addresses in this pool with base"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/size example "24"
# RET: 0

The above specification defines that we can create array elements and each can have `base` or `size`.

# Final specification code

Your specification should be complete now! After adding all the keys that are necessary for our application, you can verify that all specification keys are contained by running:

cat $(kdb file spec:/sw/docker/dockerd/#0/current)

> ‍NOTE: We want display the output because it is too long to display (~ 400-500 lines).

# Adding full example specification (with kdb import)

The above tutorial has given a good overview of how to write a specification. You might want to add a full example specification for `dockerd` using `kdb import`. To do so, follow the next steps.

To make sure we don't run into errors we will clean up everything we have done by now.

1. `sudo kdb rm -r spec:/sw/docker/dockerd/#0/current`
2. `sudo kdb umount spec:/sw/docker/dockerd/#0/current`
3. `rm -rf $PWD/dockerd` (make sure that you are in the same PWD as when you run the `sudo kdb mount`)

Now we are going to add an example of dockerd-full-spec.

Make sure you are in the root of the cloned `libelektra` repository:

1. `sudo kdb mount "$PWD/dockerd/dockerd-daemon.ni" spec:/sw/docker/dockerd/#0/current ni`
2. `kdb meta-set spec:/sw/dockerd/dockerd/#0/current mountpoint /dockerd/daemon.ni`
3. `kdb meta-set spec:/sw/dockerd/dockerd/#0/current infos/plugin "yajl"`
4. `sudo kdb spec-mount "/sw/docker/dockerd/#0/current"`
5. `sudo kdb import spec:/sw/docker/dockerd/#0/current ni < ./examples/spec/dockerd.ini`

To verify that everything was created successfully, run:

cat $(kdb file spec:/sw/docker/dockerd/#0/current)

> ‍NOTE: We want display the output because it is too long to display (~ 400-500 lines).

# Appendix (full specification)

The full specification can be viewed at dockerd-full-spec.