In the event that a service cannot watch for config file changes and reload automatically, we have to implement restart logic in a charm ourselves. Traditionally this is done in code similar to this (pseudo) code:
if container.can_connect():
try:
running_config = yaml.safe_load(container.pull(CONFIG_PATH))
except (FileNotFoundError, Error):
...
if running_config() != new_config:
container.push(CONFIG_PATH, new_config)
container.restart()
container.replan()
Alternatively, we can condense these 9 LOC to 2 LOC and 1 new LOC (_config_hash_for_auto_restart
) in the Pebble Layer:
container.push(CONFIG_PATH, new_config)
container.replan()
layer = Layer(
{
"services": {
"my-service": {
"command": f"/usr/bin/my-service --config={new_config}",
"startup": "enabled",
"environment": {
"_config_hash_for_auto_restart": sha256(yaml.safe_dump(new_config)),
},
}
},
}
)
This new approach leverages the functionality of the Pebble replan function, which only restarts (and start startup-enabled) service(s) IFF the serviceās Layer has changed. With our clever workaround, the hash of the new config file will differ from the previous hash Pebble has stored. The serviceās Layer env var _config_hash_for_auto_restart
forces workload container restarts conditionally and automatically!
This approach is inline with holistic charming i.e. reconciler pattern, also known as ārebuild the worldā.
Note: As of Python 3.7 a Dict keeping insertion order is a guaranteed language feature. Good thing we use Python >= 3.8 in charming!