A script that follows the simple
calling convention must, at a minimum,
be able to describe itself and list all resources of a certain kind. In
addition, it may be able to find a resoure by name, or update a resource.
When a simple
script is invoked, it receives a number of key/value pairs
as command line arguments, i.e. if the script is called script.prov
,
libral
will invoke it as
script.prov var1=val1 var2=val2 var3=val3
A few variables have special meaning. All special variables will have a
name starting with ral_
:
ral_action
: the action the provider should perform. This variable will always be passedral_noop
: whenever this argument is passed, regardless of its value, the provider should only determine if changes would need to be made without actually making them.
The other variables that are passed in will have the same name as the attributes of the resource.
When the script is invoked, file descriptos are set up in the following ways:
stdin
: emptystdout
: will be read bylibral
; all output described here should go therestderr
: any output on stderr will be logged. The log level can be specified by prefixing the line withLEVEL:
. Possible levels aredebug
,info
,warn
, anderror
; the log level defaults towarn
FIXME: what environment variables will be set ? What gets scrubbed ? (All but PATH and HOME ?)
Providers can implement the following actions.
describe
- describe the provider by outputting YAML metadatalist
- list all instancesfind name=NAME
- find the instance namedNAME
update name=NAME A1=V1 ... AN=VN
- update (or create or delete) resource. This action is only called if at least one attribute needs to be changed from the value reported byfind name=NAME
, and only attributes that have to be changed will be passed toupdate
.
The describe
action is used to retrieve metadata about the provider. If
the provider script.prov
is accompanied by a file script.yaml
containing the metadata, the describe
action will never be invoked. If no
such YAML file exists, the provider must support the describe
action.
You can run a provider from the command line with the following invocations:
> script.prov ral_action=describe
# dumps YAML metadata
> script.prov ral_action=list
# lists all resources
> script.prov ral_action=find name=foo
# list the resource named FOO
> script.prov ral_action=update name=foo ensure=present attr1=val1 attr2=val2
# update the resource to the state given by ensure, attr1, and attr2
# and report what had to be changed
The output from describe must be valid YAML. The simplest metadata that could be returned is
---
provider:
type: service
invoke: simple
actions: [list,find,update]
suitable: true
The entries under provider
have the following meaning:
type
: the name of the provider's underlying typeinvoke
: the calling convention the provider uses. Must besimple
for providers that use the calling convention described in this documentactions
: an array listing the actions this provider supportssuitable
: eithertrue
orfalse
indicating whether the provider can be used on the current system
FIXME: we really want to make it possible for people to write boolean
expressions for suitable
. Those expressions should have access to facts,
and some simple notation for discovering some commands, so that people can
write
provider:
suitable:
- command(which)
- command(something_else)
- osfamily == 'redhat'
Similarly, we want to make it possible to indicate whether a provider
should be the default by offering a default
key that can contain similar
expressions, so that one could write
provider:
default:
- osfamily = 'archlinux'
- osfamily = 'redhat' and operatingsystemmajrelease = '7'
- osfamily = 'redhat' and operatingsystem = 'fedora'
- osfamily = 'suse'
- bla bla bla
Note that for 'suitable' the different clauses are connected with an 'and', whereas for default they are connected by 'or'.
List must produce the following:
# simple
name: first_name
attr1: value1
...
attrN: valueN
name: second_name
attr1: value1
...
attrN: valueN
name: third_name
...
The first line of the output must be the literal string # simple
with
nothing else on the line.
Any line name: NAME
in the output is taken to indicate a new resource and
the following lines provide the attributes of that resource until the next
line name: NAME
is encountered.
When porcessing, each line is first stripped of leading and trailing whitespace, anything up to the first ':' is considered the name of the property, and anything that follows up to the end of the line, except for leading whitespace, is considered the value of the property
# simple
name: the_name
attr1: value1
...
attrN: valueN
Same format as list
, but just one resource. If the resource does not
exist (and could not possibly be created with something like
ensure=present
), the output should be
# simple
name: the_name
ral_unknown: true
# simple
name: the_name
attr1: new_value1
ral_was: old_value1
attr2: new_value2
ral_was: old_value2
...
attrN: new_valueN
ral_was: old_valueN
The output should only contain attributes that actually had to be changed,
and only needs to include attributes for which the value was changed to
something other than the value passed on the command line. For each such
attribute the new value, and the old value prefixed by ral_was
, need to
be output. As a shortcut, the provider can add a line ral_derive true
to
its output. If that flag is present, libral
will derive the changes for
any attribute that is not explicitly mentioned in the output. Those changes
will record the difference between the value of the attribute that was
reported by the last find
and the value passed into the update
action.
That means that in the simplest case, the output from update
can just be
# simple
ral_derive: true
If the variable ral_noop
was passed to the provider, no changes should be
made to the system, but the output should contain all changes that would
have been made.
If the resource does not exist, and can not be created, the output should be
# simple
name: the_name
ral_unknown: true
If the output contains any lines of the form
ral_error: some message
...
ral_eom
libral
assumes that the provider operation encountered an
error. Everything from the leading ral_error:
up to a single line
containing ral_eom
will be used as the error message.
If the output contains a ral_error:
line, libral
will disregard
anything else in the output.
Even if the script encounters an error, it still needs to exit with status code 0. Exiting with any other status code will be taken as an indication that the provider encountered a fatal error.
When the provider is invoked, care is taken that the arguments are quoted appropriately. Roughly speaking, the provider is invoked like this:
script.prov ral_action=something "arg1='value1'" "arg2='value2 spaces'" ...
The following list shows how to most easily turn these arguments into values that the provider script can use, depending on the language in which the script is implemented:
If the provider is written in Bash, use the following
eval "$@"
# all arguments are now shell variables
echo "Action $ral_action"
If the provider is written in Python, use
import shlex
import sys
args=dict(x.split('=') for x in shlex.split(" ".join(sys.argv[1:])))
print args
If the provider is written in Ruby, use
require 'shellwords'
args = Hash[Shellwords.shellsplit(ARGV.join(' ')).map { |x| x.split("=") }]
p args