How to handle charm configuration

Charms should always deploy with considered defaults that enable the application to start with as little friction as possible. That said, there are cases when it may be prudent to expose application configuration to the administrator. This should be a point of consideration for the charm developer; configuration is one of the few interfaces a charm developer can expose to an administrator, and thus it should be carefully designed.

One way to think about this is what are the necessary configuration options to configure the service the application provides. If you find the configuration specification too restrictive, and find yourself passing in lots of base64 encoded structures, you may want to rethink the approach.

Contents:

Configuration specification

As demonstrated in the “Hello, World!” guide, charm configuration is defined in a single file named config.yaml. The file should contain a single YAML map named options, in which each key is the name of a configuration setting and corresponds to a map of fields that defines the configuration option.

Each configuration option can define three fields:

Field Name Specification Required?
type Specifies the data type of the configuration option. Possible values are: string, int, float and boolean. Yes
description Contains an explanation of the configuration item and the resulting behaviour. Might also include a list of acceptable values. No
default Defines the default value for the option. Must be of the appropriate type and a sane default value in the context of the charm. No

In some cases, it may be awkward or impossible to provide a sensible default. In these cases, ensure that it is noted in the description of the configuration option. It is acceptable to provide null configuration defaults or simply omit the default field, for example:

  • default:
  • default: ‘’
  • default: ""

Example configuration specification

An example config.yaml is provided below:

options:
      name:
        default: Wiki
        description: The name, or Title of the Wiki
        type: string
      skin:
        default: vector
        description: skin for the Wiki
        type: string
      logo:
        default:
        description: URL to fetch logo from
        type: string
      admins:
        default:
        description: Comma-separated list of admin users to create: user:pass[,user:pass]+
        type: string
      debug:
        default: false
        type: boolean
        description: turn on debugging features of mediawiki

Using configuration

Configuration data can be accessed through the model by charm developers, as illustrated in the following snippet:

# ...
def _on_config_changed(self, event):
    name = self.model.config["name"]
# ...

There are a few things to consider when implementing this:

  • The config_changed event will ALWAYS happen at least once, when the initial configuration is accessed from the charm.

  • The first time the config-changed event runs may be before the pebble-ready event has completed. The config-change code should gracefully handle not being able to connect to a container/service.

  • Multiple configuration values can be changed at one time through Juju, resulting in only one config_changed event - charm code must be able to process more than one config value changing at a time.

  • If juju config is run with values the same as the current configuration the config_changed event will not run. Therefore, if you have a single config value, there is no point in tracking its previous value - the event will only be triggered if the value changes.

Configuration cannot be changed from within the charm code.

How do I know which of the config options changed?

Hi Kos,

As far as I know you can’t do this at the moment. A common pattern to for people to compare against values in StoredState or similar, but clearly you’ll want to minimise how much of config you’re replicating into StoredState.

Happy to take a look at the particular case and assist.

Jon :slight_smile:

1 Like

I am a bit skeptical about using the StoredState in the config-change hook. Let’s say two config options change causing the respective callbacks to be called. The first callback goes through and updates the the StoredState, the second callback throws an exception. This results in the StoredState not storing the value of the first config option. I guess this is why the docs [1] say:

Invocations of associated callbacks should be idempotent and should not make changes to the environment, or restart services, unless there is a material change to the charm’s configuration.

This was a problem with the old reactive framework too. From the docs [2] the semantics of the StoreState do not seem to have changed:

The Charmed Operator Framework will only persist changes in stored state to the Juju storage backend when the lifecycle event from which it is being manipulated returns successfully. If an exception is thrown that causes the event invocation to exit early, the state will not be saved.

[1] Juju | Lifecycle Events
[2] Juju | Framework Constructs

1 Like

I added some notes at the bottom to clarify how the config-changed event works

1 Like

Thanks Nick!

Perhaps we should change self.model.config to self.config in the example code?