Tools for unifying system configuration

888d755 Type: major overhaul

4 months ago

15ce544 Protocol: rename to_type/1 -> type/1

4 months ago


metasetterCore is a library of the core functionality of Metasetter, a utility to help you manage your system configuration in one place.

#System description

Metasetter provides a consistent interface to read and configure the state of a computer. The configuration of each component is managed through a provider, which statelessly represents its configuration and provides an interface to update it. Providers can be organised into provider trees, allowing configuration changes to be grouped into idempotent operations. Clients can be written to interface between users and the providers.

#Use cases

Metasetter is useful any time a program or user needs to view or change to the computer's state. By providing access to the state behind a consistent interface, programs no longer need to be concerned with the format of the configuration for a program. With metasetter, the configuration has the same representation whether derived from a config file, environment variables, a database, or something else.

Clients could range from a GUI settings menu for changing the system's state (e.g. connected networks, hostname, etc.), a toolbar showing data such as number of unread emails, or a wizard which transparently describes the operations requried to bring the system into a desired state.



[x] Build the elixir script provider

[x] Implement global service for filesystem notification, which providers can expect

[x] notify_state_change takes (pid, message) instead of (interface, message) because providers can't know which upstream interfaces they're bound to

[x] Unify all providers to having just one interface

[x] Build the GenericProvider macro, for creating providers with arguments e.g. the protocol verifying provider, the caching provider, etc.

[x] Build the Providers.Literal

[x] Refactor provider interface to have upstream_interface, downstream_interface, and will_notify as use arguments N.B. we assume that Metasetter.Core.Protocol is in the namespace of the user of the interfaces How do we print the protocol in this case?

[ ] Test that a provider crashing leads to it being restarted and handle_child_update of its parent being called

[ ] Build the router provider work out how to implement recursive protocols to represent the children

[x] Build the cache provider make the cache provider generic enables the router to not have to perform expensive get_value for each child as a generic provider

[ ] Return a promise from committers/run, rather than sync/async https://elixirforum.com/t/promises-and-resolving-them/23429

[-] Modify PubSub to work with promises, so that upstream updates can be guaranteed to have finished This isn't necessary since the provider tree is a tree only - if you send an update to a parent and then another message, they will appear in the right order There is no alternative diamond path where a message could race against another

[ ] Test children notification behaviour

[ ] Test edge behaviour of providers

[ ] Build the provider tree provider. Includes as a submodule the ProviderChildren provider, which will return a data structure containing just the children requested for a given provider. Building this will require the GenericProvider

[x] Implement children for providers through a provider - each provider has its upstream interface and downstream interface only. The downstream interface will give it its children

[x] Implement provider settings interface logic. The default handler for settings updating causes the provider to crash and get restarted, thus reloading its interface. The settings get passed directly as the init_arg to init_provider

[ ] Build the simplest possible client: sets up a provider tree with settings from an elixir script provider from a path specified on the command line. This client just displays/updates the state.


[ ] Provider.ElixirScript to notify and code reload correctly on file change, write tests

[ ] File provider to set will_notify based upon file attributes

[ ] File provider to provide file metadata as well as contents (or implement as a separate provider type?)

[ ] Work out why the up/downstream_interface can't drop the Protocol., even though it's passed as a AST to __using__ (compiler error if we do)

[ ] Make the ProviderChildren provider a generic provider, rather than having protocol any()

[ ] metasetterd - talks a simple protocol over a unix domain socket, keeping alive just a single provider tree allows hooking into parts of the tree, subtree sharing somehow, etc.

[ ] msq - the metasetter query client, talks the simple protocol

[ ] Interface protocol validation - used to ensure that provider interfaces connected together are compatible

[ ] Implement type system for update commands, so providers can indicate the allowed values, or whether it's read-only. This may or may not directly interact with the interface protocol system.

[ ] Investigate whether returning the :hibernate option for less-used providers makes sense


[ ] Provider tree inspection

[ ] Wizard generation (which may also require a general-purpose wizard provider)


Currently an example can be run by invoking mix run test.exs.

The test suite can be run with mix test, or a specific test e.g. with mix test test/metasetter-providers_file.exs

#To do

Implement protocols for interfaces


Install pre-commit, and run pre-commit install to install the hooks to lint your project and run the tests before committing.

If the linting fails, run mix format.


If available in Hex, the package can be installed by adding metasetter_core to your list of dependencies in mix.exs:

def deps do
    {:metasetter_core, "~> 0.1.0"}

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/metasetter_core.


#Why do providers have only one upstream interface?

Although having multiple upstream interfaces might seem convenient, the cost-benefit analysis on the code doesn't work out. Multiple upstream interfaces only provides a small advantage in a single case - where the settings that a provider provides are semantically distinct enough to never be wanted at the same time. An example of this could be a network provider, which provides both settings and regularly updated network diagnostics. It may be unacceptable to continually notify upstream providers of settings changes every time the network diagnostics update, and so having two interfaces seems to make sense.

However, in this case we advise that provider authors organise their data into a heirarchy to allow for O(1) equality testing of immutable parts of state which don't change. This allows filtering providers to present just a part of the data upstream.

On the other hand, having multiple upstream interfaces incurs the cost of increasing complexity of the conceptual model of metasetter, the implementation of the core code, and in the implementation of each provider.

#Why can't providers be configured with settings in their init?

To simplify the model, there is only one way of configuring a provider: through its settings provider. The provider interface for settings allows these to be changed at runtime, providing flexibility to the system. Providers which don't choose to handle the settings updates themselves will crash when their settings change, and be restarted by their supervising provider (probably ProviderTree).

Of course, sometimes it's useful to configure static settings for a provider, including at the root of provider trees. In this case, we provide the GenericProvider; this is a factory to create providers at compile time (probably provider load time) given some settings.