Ansible, from one Python script

Your whole playbook repo. One script.

Instead of editing a directory full of small YAML files, you edit one script, and either dump the playbook repo or run it outright.

Get started The reference

  1. pip install -e .
  2. imix init site.py
  3. python site.py run
# site.py
from instamix import file

file("site.yml", """
- hosts: localhost
  become: true
  tasks:
    - debug:
        msg: "Helloworld!"
""")

What it gives you

The parts of a playbook repository, as calls in one file

file() is the core. Everything else is a shorthand that calls it, or says what the script is made of.

Describe it once

Stages that run alone, variables that are held to their shape

A script says what it has and what each part is for. The inventory, the group_vars and site.yml are written from that, and a script that imports another gets all of it.

  • Every group, variable, tag and stage has a description, and it is required.
  • run refuses a tag the script does not describe: ansible itself would run nothing and say nothing.
  • Stitch scripts together with a plain import.

Describing a script

base.py
from glom import Or
from instamix import Template, file, group, stage, var

group("web", "Hosts that serve the site", 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")

stage("base", "Prepare the hosts", file("base.yml", """
    - name: Prepare the hosts
      hosts: web
      ...
"""))
describe
$ python site.py describe
Stages (run one with --tags):
  base  Prepare the hosts
  app   Install the app  [when: app_enabled | bool]

Variables of web:
  site_name: str (needed)
      The name the site answers to
  site_port: Or(int, Template) = 8080
      The port nginx listens on

$ python site.py run --tags app --limit web1

See it work

In the editor, and in a run

imix lsp runs next to your Python server, and each string is the file it declares: completed, linted and documented as Ansible. Then the same script runs.

A playbook written in Neovim: completion inside the string, an ansible-lint problem on its task, module docs on hover.
A run of examples/webserver2.py.

Beyond one machine

From a script to a run that someone allowed

The same script runs from your shell, or from a catalogue, through a gate. Nothing in between writes a command: a job is the script, the stage, the hosts and the values.

The screening proxy

Only what a list lets through

imix proxy serve stands between ansible-galaxy, uv and pip and the real Galaxy and PyPI. It caches what they download and serves only what a list of rules allows. The first line that matches decides.

  • Refused versions are left out of version lists; a refused download is a 403, and it is logged.
  • A script can pin its downloads to the list named after its own SHA-256.
  • A blacklist from OSV keeps malicious PyPI packages out.

The screening proxy

imix proxy -l web edit
# web servers
allow collection nginxinc.nginx_core <0.8
allow collection community.*
deny any *evil*
allow pip requests >=2.30
default allow role

For other programs

Every command speaks JSON, and there is a contract

With --jsonl, stdout carries one event per line. An OpenAPI document describes each command, each event and each declaration call, and the tests keep it from drifting.

The reference on this site is read from that same document.

The events

--jsonl
$ python site.py run --jsonl -C
{"event":"exec","cmd":["ansible-playbook","-i","inventory","--check","site.yml"],"cwd":"/tmp/instamix-..."}
{"event":"output","stream":"stdout","line":"PLAY [localhost] ****..."}
{"event":"exited","cmd":["ansible-playbook","-i","inventory","--check","site.yml"],"status":0}

Start with one file

Write a starter script, or absorb the playbooks you have.

$ imix init site.py
$ imix import ./old-playbooks -o site.py

Read the manual See the examples

    Type to search. ↑ ↓ to move, enter to open.