Configure snaps with confdb

Confdb is a configuration system that allows snaps to be configured in fine-grained ways using a confdb-schema assertion.

This guide will detail the steps required to use confdb, as well as explore some of its features. We’ll use an example where one snap creates a Wi-Fi configuration that another snap can view.

Prerequisites

Confdb is currently considered an Experimental feature.

We first need to ensure the latest version of snapd is installed. This is important as confdb is still being actively developed:

snap refresh

We can then enable confdb with its experimental feature flag:

sudo snap set system experimental.confdb=true

Creating a confdb-schema assertion

To use confdb, we must first create a confdb-schema assertion.

There are two main parts to a confdb-schema assertion:

  • View rules
  • Storage schema

The view rules determine the “schema” of the configuration namespace exposed to the snap, i.e., what paths can be accessed and how they’re mapped onto the storage schema.

The storage schema determines what the actual stored data looks like and what constraints apply.

We’ll start by defining a storage schema.

Storage schema

When creating the storage schema, we can constrain the format and values that the stored data can take, such as the characters you can include in a password.

For example:

  {
    "storage": {
      "aliases": {
        "password": {
          "type": "string",
          "pattern": "^[\w~!@#\$%\^&-\+=\;:,\.\/\?]{8,63}$"
      },    
    },
      "schema": {
        "wifi": {
          "schema": {
            "psk": "$password",
            "ssid": "string",
            "state": {
              "type": "string",
              "choices": [
                "up",
                "down"
              ]
            }
          }
        }
      }
    }
  }

The above schema describes three configuration paths: an SSID, a password and a connection state.

Snapd expects the storage schema to conform to a very precise format, using 2 spaces for indentation and sorting map entries. Both Python 3’s json.dump and Golang’s json.MarshalIndent can be used to produce this format. jq -S can also be used to sort the schema. It’s useful to keep the storage schema in its own file so we can modify it over time.

See confdb-schema types for a detailed description of types and contrains.

View rules

Now let’s create some view rules to access confdb.

In our example, we’ll use one snap to configure the network and another to access, which means we need two views: configure-wifi and access-wifi.

  • configure-wifi exposes parameters and allows them to be set.
  • access-wifi allows the snap to list Wi-Fi connections and read SSID and state information (e.g., up, down).

Both of these can be defined as follows:

type:         confdb-schema
authority-id: <account-id>
account-id:   <account-id>
name:         network
summary:      Configure network parameters
timestamp:    2025-04-02T19:31:32Z
views:
  configure-wifi:
    summary: Configure Wi-Fi networks
    rules:
      -
      request: "{name}.ssid"
      storage: "wifi.{name}.ssid"
      -
      request: "{name}.password"
      storage: "wifi.{name}.psk"
      -
      request: "{name}.state"
      storage: "wifi.{name}.state"

  access-wifi:
    summary: List and read Wi-Fi SSIDs
    rules:
      -
      request: "{name}"
      storage: "wifi.{name}"
      access: read
      content:
        -
        storage: ssid
        access: read
        -
        storage: state
        access: read

body-length: 552
sign-key-sha3-384: 74KHeq1foV…

See Access Ubuntu One for details on retrieving your account-id.

Putting it together

Now we’re ready to create the assertion. Run the following command, filling in the account ID and key name:

snapcraft edit-confdb-schema <account-id> network --key-name=<key-name>

Next, edit the template assertion, replacing the views and storage schema with the ones we defined above.

The end result should look something like the following:

account-id: <account-id>
name: network
views:
  configure-wifi:
    rules:
      -
        request: "{name}.ssid"
        storage: "wifi.{name}.ssid"
      -
        request: "{name}.password"
        storage: "wifi.{name}.psk"
      -
        request: "{name}.state"
        storage: "wifi.{name}.state"

  access-wifi:
    rules:
      -
        request: "{name}"
        storage: "wifi.{name}"
        access: read
        content:
          -
            storage: ssid
            access: read
          -
            storage: state
            access: read

body: |-
  {
    "storage": {
      "aliases": {
        "password": {
          "pattern": "^[a-zA-Z0-9~!@#\\$%&-=:,\\.]{8,63}$",
          "type": "string"
        }
      },
      "schema": {
        "wifi": {
          "values": {
            "schema": {
              "psk": "$password",
              "ssid": "string",
              "state": {
                "choices": ["up", "down"],
                "type":"string"
              }
            }
          }
        }
      }
    }
  }

Then exit the editor and sign the assertion when prompted.

Creating the snaps

See Create a new snap for a general overview of the snap creation process.

There are two things a snap needs to be able to use confdb:

  • a plug to declare its intent to use a confdb namespace and
  • an optional set of hooks that snapd invokes when the namespace is accessed

Custodian snap

The interface plug specifies an account and a view which together identify the view through which the configuration is being accessed. It can also optionally declare a role, with a value of custodian.

Custodian snaps have a special role when accessing ephemeral data. They’re responsible for saving or loading this data to an external source outside of snapd.

The data that snapd stores for ephemeral configuration is only a cached, non-authoritative version of the external data. At least one custodian snap must be installed in the system for a particular confdb-schema.

The first snap will configure the Wi-Fi network to be used, so let’s configure it accordingly:

plugs:
  configure-wifi:
    interface: confdb
    account: <account-id>
    view: network/configure-wifi
    role: custodian

The other component that must be defined in the snap are the hooks.

Snaps interact with confdb through snapctl and, in turn, snapd invokes various hooks when confdb is read from or written to.

In this case, the custodian snap will only define the change-view-<plug>, which provides an opportunity for the snap to modify values being set.

As an example, we’ll say that the SSID must be prefixed with our company’s name: “Acme”. The configuration can be set by the administrator using snap set or by any other snap with access to that view, so it’s the custodian snap’s responsibility to enforce that the prefix is enforced.

Our change-view-configure-wifi hook looks like this:

#!/bin/bash -xe

# prefix the SSID with "acme", if not already present
value=$(snapctl get --view :configure-wifi acme.ssid)
if [[ "$?" -eq 0 && "$value" != acme-* ]]; then
  snapctl set --view :configure-wifi ssid="acme-$value"
fi

That’s it for the custodian snap.

Reader snap

Now we’ll create a snap that will read the configuration. Its plug will reference the read-only view and it will omit the role:

plugs:
  access-wifi:
    interface: confdb
    account: <account-id>
    view: network/access-wifi

Non-custodian snaps can only define an observe-view-<plug> hook, which allows them to be notified when the confdb-schema referenced by that plug has been used to modify data.

#!/bin/sh -xe

# read the new SSID and store it somewhere
new_ssid=$(snapctl get --view :access-wifi acme.ssid)
echo "$new_ssid" >> "$SNAP_COMMON"/ssid

Install and connect the snaps

We can mediate snap access to confdb-schema views by connecting their confdb plugs.

Note that when a snap is published by the same account ID as the assertion, the interface plug will be auto-connected.

snap install custodian-snap reader-snap 
snap connect custodian-snap:configure-wifi 
snap connect reader-snap:access-wifi 

Setting data

Now we’re ready to start setting data, we’ll use snap run --shell to mimic the snaps interacting with confdb:

$ sudo snap run --shell custodian-snap.sh
# snapctl set --view :configure-wifi acme.ssid=some-ssid acme.password=super-secret
# exit

$ sudo snap run --shell reader-snap.sh
# snapctl get --view :access-wifi acme.ssid
acme-some-ssid
# exit

$ snap get <account-id>/network/access-wifi acme.ssid
acme-some-ssid

As expected, the change-view-configure-wifi hook was invoked when custodian-snap modified the SSID and prefixed the value with “acme”.

We were also able to read the same value from both another connected snap and through the snapd API using snap get.

We can also check that the reader-snap’s observe-view-access-wifi hook was invoked when the SSID was changed and that it saved the new value in SNAP_COMMON:

$ snap changes
ID   Status  Spawn               Ready               Summary
123  Done    today at ...        today at ...        Set confdb through "<account-id>/network/configure-wifi"
...

$ snap change 123
Status  Spawn  Ready  Summary
Done    ...    ...    Clears the ongoing confdb transaction from state (on error)
Done    ...    ...    Run hook change-view-manage-wifi of snap "custodian-snap"
Done    ...    ...    Run hook observe-view-manage-wifi of snap "test-snap"
Done    ...    ...    Commit changes to confdb (NI7Jstuu8gffcoXr02i1kYt898p6Co0A/network/wifi-setup)
Done   ...    ...   Clears the ongoing confdb transaction from state


$ cat /snap/reader-snap/common/ssid
acme-some-ssid
2 Likes

I just wanted to thank @miguelpires for writing all the above documentation, and for all the associated confdb documentation.

1 Like