Libral is a sketch of an attempt to try and build a native provider platform/framework. The goals of this are
- Roughly match the functionality of the
puppet resourcecommand - Serve as the basis for Puppet's provider system
- Avoid being Puppet specific as much as possible to make it useful for other uses
- Make writing providers very easy
- Mailing list: libral
- IRC:
irc.freenode.net:#libral
Please see this document for instructions on building libral
from source.
If you just want to quickly try out libral, you can download a
precompiled tarball
(GPG signature)
that should work on any Linux machine that has glibc 2.12 or later. (It
might actually work with glibc 2.8 or later) If you succeed in that, please
let us know.
After you built libral you can try things out by running ralsh:
export RALSH_DATA_DIR=$LIBRAL_CHECKOUT/data
# list available types
./bin/ralsh
# list all instances of a type
./bin/ralsh mount
# list a specific instance
./bin/ralsh service crond
# make a change for the better
./bin/ralsh service crond ensure=stoppedThe script examples/dpack produces a directory build/dpack that has a
statically linked ralsh plus all supporting files in it. You can copy
build/dpack into a container and then run it with commands like the
following:
CONTAINER=<some_container>
docker cp build/dpack $CONTAINER:/tmp
docker exec $CONTAINER /bin/sh -c "RALSH_DATA_DIR=/tmp/dpack/data /tmp/dpack/bin/ralsh"- finish mount provider
- add a shell provider
- add a remote provider (using an HTTP API)
- adapt providers to multiple OS (maybe using mount)
- more core providers
- cron
- file
- group
- host
- package
- service (besides systemd)
- user (userXXX)
- even more core providers
- interface
- k5login
- mailalias
- selboolean
- selmodule
- sshkey
- ssh-authorized-key
- vlan
- yumrepo
- noop mode
- event reporting
Naming things is hard; here's the terms libral uses:
- Type: the abstract description of an entity we need to manage; this is the external interface through which entities are managed. I am not entirely convinced this belongs in libral at all
- Provider: something that knows how to manage a certain kind of thing
with very specific means; for example something that knows how to manage
users with the
user*commands, or how to manage mounts on Linux - Resource: an instance of something that a provider manages. This is closely tied both to what is being managed and how it is being managed. The important thing is that resources expose a desired-state interface and therefore abstract away the details of how changes are made
FIXME: we need some conventions around some special resource properties; especially, namevar should always be 'name' and the primary key for all resources from this provider, and 'ensure' should have a special meaning (or should it?)
- Do we need types at all at this level ?
- Can we get away without any provider selection logic ? There are two
reasons why provider selection is necessary:
- adjust to system differences. Could we push those to compile time ?
- manage different things that are somewhat similar, like system packages and gems, or local users and LDAP users ? This would push this modelling decision into a layer above libral.
- Wouldn't it be better to make providers responsible for
noopmode rather than making it part of the framework ? - Would it be better to make providers responsible for event generation rather than doing that in the framework ?
What resources libral can manage is determined by what providers are
available. Some providers are built in and implemented in C++, but doing
that is of course labor-intensive and should only be done for good
reason. It is much simpler, and recommended, that new providers first be
implemented as external providers. External providers are nothing more than
scripts or other executables that follow one of libral's calling
conventions. The different calling conventions trade off implementation
complexity for expressive power.
The following calling conventions are available. If you are just getting
started with libral, you should write your first providers using hte
simple calling convention.
- simple
augeas(planned,maybe): when you mostly need to twiddle entries in a file, and maybe run a commandjson(planned): input/output via JSONjson_batch(planned,maybe): input/output via JSON, can operate on multiple resources at once
The intent is that the lifecycle for providers will be roughly something like this:
some_provider prov();
// A call to suitable() must also initialize any provider internals.
// Once suitable() returns true, the provider must be ready to use.
if (prov.suitable()) {
// Loop over all resources
for (auto rsrc : prov.instances()) {
auto should = get_new_attrs_from_somewhere();
rsrc.update(should);
}
// or do something to a specific resource
auto rsrc = prov.find(some_name);
rsrc.destroy();
// or create a new one
auto rsrc = prov.create("new_one");
auto should = get_new_attrs_from_somewhere_else();
rsrc.update(should);
rsrc.flush()
// make sure all changes have been written to disk
prov.flush();
// not sure yet if we need an explicit 'close' call
prov.close()
}- Rather than returning just a
bool, at some pointsuitable()will need to return more details about what the provider does and whether it can be used - At some point, we'll need a notion of a context that tells providers about system details (like facts) and some settings; would be cool to use that to change the idea of where the root of the FS is, for example.