Describing a script
Groups, variables with a shape, tags, and stages that run alone. The inventory, the group_vars and site.yml are written from what the script says it has.
A script can say what it has and what each part is for: its host groups,
its tags, the variables it needs and the stages it runs in. The inventory,
the group_vars and site.yml are then written from that, and a script that
imports another gets all of it.
# base.py
from glom import Optional, Or
from instamix import Template, file, group, stage, tag, var
group("web", "Hosts that serve the site",
children=["web_front"], vars={"ansible_user": "root"})
group("web_front", "The ones the load balancer sends to", hosts=["web1", "web2"])
var("site_name", "The name the site answers to", group="web")
var("site_port", "The port nginx listens on", Or(int, Template), default=8080, group="web")
var("site_ntp", "Where the time comes from",
[{"hostname": str, Optional("pool"): bool}], default=[], group="web")
tag("site_reload", "Only reload nginx")
stage("base", "Prepare the hosts", file("base.yml", """
- name: Prepare the hosts
hosts: web
...
"""))
Every one takes a name and a description, and the description is required.
Groups#
group(name, description, hosts=, children=, vars=) describes a host
group. children nests groups; hosts is a list of names or
{name: {inventory variables}}. Describing a group again, in another
script, adds its hosts, children and vars to it. The groups become the
inventory (an INI file with the descriptions as comments), and their
vars go into group_vars/<group>.yml.
Variables#
var(name, description, shape=str, default=, group="all") describes a
variable. The shape is what glom’s Match
takes: a type, a list or dict of shapes, Or(...), Optional(key) for a
key that may be missing.
- A list of a shape,
[str], is zero or more of it;NonEmpty([str])is one or more, andNonEmpty(str)a string with something in it. Templatestands for a value that ansible templates, soOr(int, Template)is a number or a template for one.- A
defaultgoes into the group’s group_vars, under its description. - Without one the variable is needed:
group(vars=...)of that group, or of one it is nested in, has to set it, here or in a script that imports this one.optional=Truelets it stay unset.
Tags and stages#
tag(name, description) describes a tag that the playbooks use.
stage(name, description, playbook, when=, tags=) describes a stage: a
playbook the script declares (default NAME.yml) that does one thing.
site.yml imports the stages in the order they were described, each under
a tag of its name. when is a condition on the hosts’ variables, for a
stage that a variable switches off.
Stitching scripts together#
# site.py
import base # its groups, variables and stages come first
from instamix import file, group, stage, var
group("web", "Where the app runs", vars={"site_name": "example.com"})
var("app_enabled", "Whether the app is installed", bool, default=True, group="web")
stage("app", "Install the app", file("app.yml", "..."), when="app_enabled | bool")
base.py still runs by itself when it sets what it needs. python site.py describe lists the whole; describe --json writes it as one JSON object
on one line ({"stages": [...], "tags": [...], "groups": [...], "variables": [...]}), and describe --jsonl as an event for each:
$ python site.py describe
Stages (run one with --tags):
base Prepare the hosts
app Install the app [when: app_enabled | bool]
Groups:
web Hosts that serve the site
web_front The ones the load balancer sends to (web1, web2)
Variables of web:
site_name: str (needed)
The name the site answers to
site_port: Or(int, Template) = 8080
The port nginx listens on
Running one stage#
$ python site.py run --tags app --limit web1 # one stage, one host
$ python site.py run --skip-tags base
$ python site.py run -e app_enabled=false # a variable switches a stage off
run refuses a tag the script does not describe (ansible itself would run
nothing and say nothing), and it refuses to start when a needed variable is
not set or a value has not its shape. Only the values the script gives are
checked: what -e or another inventory brings is not.
lint checks the same, and that every tag the playbooks use is described
(always and never are ansible’s own), that every stage has its playbook
and that every nested group is described.