The simple calling convention
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=val3Special variables
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.
stdio
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
Environment
FIXME: what environment variables will be set ? What gets scrubbed ? (All but PATH and HOME ?)
Actions
Providers can implement the following actions.
describe- describe the provider by outputting YAML metadatalist- list all instancesfind name=NAME- find the instance namedNAMEupdate 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 changedOutput from describe
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: trueThe entries under provider have the following meaning:
type: the name of the provider's underlying typeinvoke: the calling convention the provider uses. Must besimplefor providers that use the calling convention described in this documentactions: an array listing the actions this provider supportssuitable: eithertrueorfalseindicating whether the provider can be used on the current system
Digression on suitable/default (later)
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 blaNote that for 'suitable' the different clauses are connected with an 'and', whereas for default they are connected by 'or'.
Output from list
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
Output from find
# 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
Output from update
# 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
Error reporting
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.
Getting at the command line arguments
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 argsIf the provider is written in Ruby, use
require 'shellwords'
args = Hash[Shellwords.shellsplit(ARGV.join(' ')).map { |x| x.split("=") }]
p args