potion
potion
Potion is a single application to configure a system, like Puppet or Chef, but minimalist.
Usage
There are two ways to use potion:
- You can use
potion
to run a recipe. A recipe is a script which contains the description of the configuration that you want to apply. - You can use potion to create a standalone autoexecutable recipe, so you don’t need potion to run a potion file.
Using potion to run a recipe
potion run Potionfile Otherrecipe.potion
The recipe is any bash script which can contain a number of specific funtions, listed below in API Reference.
Using potion to create a self-executable recipe
potion create Potionfile > recipe.sh
Using potion to run a local recipe in remote host
potion -q create Potionfile | ssh -tt hostname
Using potion with remote recipes
potion run https://miremotehost/remoterecipe.potion
Or more complex example with ssh and git:
potion run ssh+git://myremoterepohost/mirepo.git
When you point to a repo, a file called init.potion
in the root dir of the repo is loaded automatically, as potion starting point.
Using default recipes
export POTION_DEFAULT_SOURCE="ssh+git://myremoterepohost/mirepo.git"
potion run
Using potion to run periodically
Potion does not support daemon mode, but you can use systemd timers or cron jobs to run potion every hour or when you prefer. Please note that potion requires some environment variables like HOME
. Is a good practice tu run potion with sudo as unprivileged user, for example:
id
$ uid=1000(user) gid=1000(user) groups=1000(user)
sudo -i potion run script.potion $
Writing recipes
Potion recipes are, in fact, bash scripts, so any thing that you can create with a bash script could be used in recipe, nevertheless we recommend you to use potion API to more common situations, and create new modules over them. Recipes are evaluated in order, but you parallelize actions using context (read more below).
Contexts
A context is a new process running by potion to perform some task in parallel. You can configure context with the following syntax in the Potion file:
context @foo
{
# do some actions
}
context @bar
{
# do other actions
}
In this example we create two context which will be running in parallel in the host. Please note that context must be start with @
character.
Artifacts
An artifact is a set of files which should be included in the potion file and deployed by Potion application. You can think an artifact like a template for file. You can use artifacts like any other file, in the form:
file::present /etc/config.json \
data="artifact://config.json"
This code will create (if doesn’t exist yet) a fil in /etc/config.json
, which content is generated from artifact called config.json
. The artifacts directory must be specified by command like when called potion, like the following example:
potion run -a ./artifacts_dir Potionfile
If you generate a self-executable potion file, the artifacts will be included in the script.
Instead of artifact://
url, you can use artifact+eval://
url, which allows you to expand variables inside the artifact file. You can use normal $variable
form, or even run more complex like $(if $var; then echo value; fi)
.
You can also load artifacts programatically from the potion recipe, using artifact::load
:
artifact::load ./path/to/artifactsdir
Secrets
Secrets are intented to keep some variables of your config in secret. Potion will add secret variables using module secret
, documented in the API. The secret file is just another potion file which contains secret::add
commands, which define secret values in plain.
There are three different ways to use secrets:
Keep you secret file out of the recipes, and copy on each host individually. The run potion with
-s
(--secret
) flag pointing to the secret file.Use symmetric encryption (recommended method). Potion will read a passphrase from
/etc/potion.key
(or whereSECRET_MASTERKEY
environment variable defines) and use that passphrase to decrypt the secret file (using AES256 via openssl).Using asymetric encyption via GnuPG, using different keyfiles for each host. This is the most secure way, but hard to maintain.
Recomended option is to set a plain secret.potion
file outside your recipes repository and encrypt it with:
openssl enc -e -a -aes256 -in secret.potion -out secret.potion.aes
And keep secret.potion.aes
(encrypted file) under version control system (The -a
flag in openssl indicates to encode the file using base64, which is used by potion too). This methid provide a reasonable security and a goo way to manage the secrets from central repository.
Of course, you can load secrets programatically, for example:
secret::load "crypt+file://$PWD/secrets.potion.aes"
This example using openssl encrypt (crypt
) method to decrupt secrets file. In API documentation, under module secret
you can see different load formats, including plain and gpg ones.
Including other potion files
You can split your config in different modules, and you can load them from the starting potion file (init.potion
if you are loading recipes from repository). See the include
module to learn in which ways you can include other modules, but in general you will use the following:
include::path ./modules/\*.potion
Testing changes
Before to apply changes, you probably want to test the changes, to do that you can run potion with --pretend
flag. If you are running and self-executable potion, you can use the environment variable $PRETEND=true
to ensure that the potion just pretending instead on real running.
Bonus: How structure your recipes
There is no unique way to structure your recipes, depends of how complex your configuration is, but in general, this is a good approach:
- Put your artifacts under
artifacts/
directory - Create modules under
modules/
to configure some software (i.e. modulenginx
). Module should have a::present
,::exists
and::absent
functions. - Create modules under
profiles/
to help you to group core actions. For example profilewebserver
will use modulenginx
and other to configure a webserver. - Create modules under
roles/
to group profiles. For examplefrontend
role will use webserver and others to configure what a frontend server is. - Finally modules under
nodes/
and named like FQDN of the node, will include roles that defined those server.
Of course, you can follow any other approach that be convenient for you, this is just a recommendation.
External API Reference
dir
The dir
module provides functions to ensure that specified directory is created or destroyed.
Usage example
dir::present "/mnt" mode=0755
dir::present [name=]<name>
[options]
ensure that specified directory name passed as argument exists, and create it if not.
Parameters
name
- the name of the directory to be created.
mode
- the four digit octal mode of the new directory (0755) by default.
user
- the user owner the directory (root by default).
group
- the group owner the directory (root by default)
dir::absent [name=]<name>
ensure that directory named as arguement, does not exist, removing it if necessary.
Parameters
name
- the name of the directory to be removed
dir::exists [name=<fname>]
return true if directory name passed as argument exists, or false otherwise.
file
The file
module provides functions to ensure that specified file exists or not in the system and also has specified content. To creat content, use the content
parameter, usually this parameter accepts a URL (in the usual curl accepted format or artifact://
to read from static artifact or artifact+eval://
to read from an evaluated artifact.
Usage example
file::present /etc/somefile \
content="artifact://someartifact"
file::present /etc/otherfile \
content="some string content"
file::exists [name=]<fname>
[options]
return true if file exists, or none otherwise
Parameters
name
- the filename to check for.
content
- the content URL
file::present [name=]<name>
[options]
ensure that file passed as argument exists.
Parameters
name
- then file name to ensure
user
- the user owner the file
group
- the group owner the file
mode
- the four digit octal mode of the file (i.e. 0644)
content
- the content url
file::absent [name=]<name>
[options]
ensure that file passed as argument does not exist.
Parameters
content
- the content url (only removes the file if content is the same).
file::lines::present [name=]<name>
[lines]
ensure that specified lines (one per argument) are present in the file.
Usage example
file::lines::present "/etc/sudoers" \
"%wheel ALL=(ALL) NOPASSWD: ALL"
"root ALL=(ALL) ALL"
file::lines::absent [name=]<name>
[lines]
ensure that specifie lines are NOT present in the file.
Usage example
file::lines::absent "/etc/sudoers" \
"%users ALL=(ALL) NOPASSWD: ALL"
format
The format
module provides a way to format and mount specified block devices.
Usage example
format::present /dev/sda1 fstype=xfs
format::exists [name=]<device>
[options]
return true if device exists and it’s mounted on mountpoint
Parameters
name
- the device name
format::present [name=]<device>
[options]
ensure that specified device has format
Parameters
name
- the device to be initialized
fstype
- the filesystem type to format the device
format::absent [name=]<device>
ensure that specific device is not formatted.
group
The group
module offers a way to ensure that specified group of uses is created or not in the system.
Usage example
group::present mygroup
group::exists [name=]<groupname>
return true or false if the specific groupname exists or not, respectively.
group::present [name=]<groupname>
system=<true|false>
[gid=gid]
create group if not exists
Parameters
name
- the name of the group to create.
system
- if true create a system group.
gid
- the GID of the new group
group::absent [name=]<groupname>
destroy groupname if exists
host
The host
module provides functions to configure hostname of the machine.
Usage example
host::present myhost.mydomain.com
host::exists [name=]<hostname>
return true if the FQDN of the node is equal to specified one.
host::present [name=]<hostname>
ensure than the node has the specified hostname.
host::absent [name=]<hostname>
ensure than the node has not the specified hostname.
include
The include
module allows you to include other potions from remote sites.
include::path <glob_expr>
include all files matches with glob expression
link
The link
module provides functions to ensure that specified symlink exists in the system and points to specified file.
Usage example
link::present /mylink target=/etc/motd
link::exists [name=]<link>
target=<source>
returnt true if the specified link exists and point to source.
Parameters
target
- the target where link points to
link::present [name=]<link>
target=<source>
ensure that the link exists and point to source.
Parameters
target
- the target where link points to
link::absent [name=]<link>
target=<source>
ensure that the specified link does not exists or not point to specified source.
Parameters
target
- the target where link points to
mount
The mount
module provides functions to ensure that specific mountpoint is mounted.
Usage example
mount::present /mnt device=/dev/sda1 fstype=xfs
mount::exists [name=]<name>
[opts]
return true if specific device is mounted with passed options
Parameters
device
- the device to mount
fstype
- the fstype of the device filesystem (default: auto)
mount::present [name=]<name>
[opts]
ensure that specific device is mounted according with passed options.
Parameters
device
- the device to mount
fstype
- the fstype of the device filesystem (default: auto)
mount::absent [name=]<name>
ensure that specific mount point is not mounted.
pkg
The pkg
module provides functions to ensure package installation on the system.
Usage example
pkg::present vim
pkg::exists [name=]<package>
return true if the specific package is present in the system.
pkg::present [name=]<package>
ensure that specified package is installed on the system.
pkg::absent [name=]<package>
ensure that specified package is not present on the system.
refresh
The refresh
add functionality to refresh services or other resources.
refresh <resource>
refresh specified resource
secret
The secret
module allows you to get/set passwords or secret config keys from diferent sources instead of save them in plain text in the potion file.
Environment variables
SECRET_MASTERKEY
- Path to file where the master key for secrets lives. By default
/etc/potion.key
secret::dump [name=]<name>
dump the encrypted file of a secret plain file.
secret::load [name=]<name>
load a secret file
secret::load_inline <format>
<content>
load scripts from the inline string. Available formats are plain, crypt or gpg.
secret::add [name=]<name>
content=<content>
add new secret with specified content associated with key passed as argument.
secret <name>
return the secret text associated with specified name
srv
The srv
module provides high-level functions to manage package installations.
Usage example
srv::present sshd
srv::exists [name=]<service>
return true if the specific service is present in the system.
srv::present [name=]<service>
ensure that specified service is installed on the system.
srv::absent [name=]<service>
ensure that specified service is not present on the system.
swap
The swap
module contains functions to create and configure swap space.
Usage example
swap::present \\
/tmp/swapfile \\
size=1G \\
format=true
swap::exists
return true if swap is enabled
swap::present [name=<device|file>]
[size=<size>]
[format=<true|false>]
ensure that swap is enable in specified device or file and specified size.
Parameters
name
- the device use as swap space.
size
- the size required if swap file is created
format
- if true, format swap space.
swap::absent [name=]<device|file>
ensure that specified device is not enabled for swap
Parameters
name
- the device use as swap space.
tz
The tz
module provides functions to configure timezone in the system.
Environment variables
TZ_DIR
- path where zoneinfo live. By default
/usr/share/zoneinfo
.
Usage example
tz::present Europe/Madrid
tz::exists [name=]<zonename>
if timezone especified in argument is set return true, otherwise return false.
tz::present [name=]<zonename>
ensure that specified timezone is set.
tz::absent [name=]<zonename>
ensure that the specified timezone is not set.
user
The user
module provides functions to manage users in the system.
Usage example
group::present mygroup
user::present someuser \
groups=adm,mygroup \
nohome=true \
system=true \
shel=/bin/false
user::present [name=<username>]
[options]
create user if not exists
Parameters
basedir
- set the base director to create home directories.
gecos
- set the GECOS field in user record.
groups
- a comma separated list of groups for the user.
nohome
- if true do not create home directory (false by default).
system
- if true create system user (false by default).
uid
- if set, try to force that UID as user id for the new user.
shell
- set the shell of the username.
password
- the password for the user
user::absent [name=]<username>
[keepdata=true]
destroy username if exists
Parameters
keepdata
- if true do not remove user data.
Internal API Reference
artifact
The artifact
module contains function to access to artifacts. An artifact is a file embedded in potion compiled object, usually you want to set some configuration files as artifacts.
artifact::hmac <artifact_name>
print to stdout the HMAC of the artifact passed as argument.
Environment variables
ALGO
- set the algorithm to use for calculate HMAC. Valid values are: sha1, md5, sha256, sha512. The proper helper tool must be installed on the system.
artifact::extract
extract artifacts if artifact support is enabled.
artifact::exists <artifact_name>
return true if the artifact exists, false otherwise.
artifact::read <artifact_name>
read a specific artifact from potion.
artifact::eval <artifact_name>
read and eval specific artifact from potion.
artifact::build <directory>
scan the directory passed as argument and return a POTION_ARTIFACTS variable to stdout with the encoded version of the artifacts.
artifact::load <directory>
load in environment potion artifacts
context
This internal module provides functionality to run different context in paralell.
context::task::enqueue <fun>
enqueue a task to be done when context finished.
context::task::apply <context>
apply functions enqueued by context::task::enqueue.
context::execute <name>
Execute a context named as name.
Environment variables
CONTEXT
- contains the context name, and this variable will be propagated to other functions in the same context.
context::run
run all context in paralell.
deprecated
This internal module provides a way to mark deprecated features or modules.
deprecated [name=]<name>
[version=version]
[date=date]
set a deprecation mark called name (could be a message), when potion version is lower to specified version a warning is raised, otherwise execution fails with an error. The same behaviour can be used with a date in the format %y-%m-%d, until that date the deprecation warning message will show, after that date execution breaks.
main
The main
module contain the specified potion body.
main::init
print nice potion logo and some custom messages at the beginning.
Environment variables
POTION_DEFAULT_SOURCE
- If set, always try to get potion files from this url. Note that if ther arguments are passed in the command line, potion will load all of them plus the defined in
POTION_DEFAULT_SOURCE
. VERSION
- a readonly variable which contains the potion version number.
meta
This internal module provides information about metadata. The metadata information is used internally by potion to store data discovered from the system, like num of CPUs, the hostname, etc. This is an internal module, so you might be not need to override metadata ever.
meta::get <key>
get the current value of key
meta::set <key>
<value>
add new value to the metadata
meta::polulate
poplulate metadata values from the system
res
The res
module provides a way to work with potion resources.
res::variables <var=value>+
pretty priting resources variables
res::created <resource>
print the properly message when resource is created
res::deleted <resource>
print the properly message when resource is deleted
res::refreshed <resource>
print the properly message when resource is refreshed
res::override <resource_name>
<function_name>
override a resource with another function, the new function will receive the same parameters than the original resource. The original one can be invoked with \resource_name
(prepending a backslash).