Paternoster, named after the lift, enables me to quite easily create a “program” which uses the Ansible API to actually invoke and run a playbook, mostly hiding this fact from the user. I’ve known of Paternoster since at least March 2018 when I PRed one of my infamous one-letter fixes and have since wanted to blog about it. Here goes.

$ pip install paternoster ansible

The executable playbook I create has a hash bang which points to Paternoster. It can be specified in a sudoers file, say, to give it specific privileges. Other than that, the playbook in my “program” contains two plays: the first defines Paternoster parameters which become program options, the second and subsequent are the typical Ansible plays which Paternoster invokes with variables.

The host paternoster doesn’t actually exist in the inventory – it’s a magic word which triggers Paternoster to set up its parameters.

#!/usr/bin/env paternoster

- hosts: paternoster
  vars:
      description: Create a user
      parameters:
        - name: uname
          help: Username to create
          prompt: true
          type: paternoster.types.restricted_str
          type_params:
              regex: "^[A-Z]+[0-9]$"
              minlen: 2
              maxlen: 5
  
- hosts: localhost
  connection: local
  tasks:
    - name: Create user
      debug:
          msg: "Will create user "

Parameters are represented by a dictionary which I found quite self-explanatory. They are passed to Ansible prefixed with param_, so my uname parameter becomes the variable param_uname in Ansible. Standard Python types can be used for describing variables (e.g. int), but Paternoster provides a few others in order to enforce, say, string validation.

For example, paternoster.types.restricted_str can be validated with a regex or have certain character classes, a minimum and maximum lengths. The domain type is checked for a DNS domain, the url type for a URL, etc.

Parameters can also be prompted for, and as with Ansible’s vars_prompt, if the parameter’s value is passed on the CLI as an option Paternoster doesn’t prompt for it but does validate the value according to my specification.

Assuming the above script is called mkuser, the following could occur:

$ mkuser -h
usage: mkuser [-h] [--uname UNAME] [-v]

Create a user

optional arguments:
  -h, --help     show this help message and exit
  --uname UNAME  Username to create
  -v, --verbose  run with a lot of debugging output


$ mkuser 
Uname: jane8
usage: mkuser [-h] [--uname UNAME] [-v]
mkuser: error: argument --uname: invalid string value: 'jane8'

$ mkuser --uname JANE8
Will create user JANE8

The first invocation shows help with the descriptions I specify in the first play. The second shows how I’m prompted for a variable and how validation (regex, lengths) fails, and the third then finally works because it’s upper-case only, the right length and ends in a digit.

Paternoster’s check_user verifies that the user running the script is the one specified, which allows me to have programs only a particular user can execute, and if I set become_user, Paternoster uses sudo(1) to execute the playbook as the specified user. If set, success_msg is printed on successful exit as a confirmation to the user, untemplated.

What I like about Paternoster is how it basically “hides” that an Ansible playbook is being invoked, and that it wraps that into a utility which I can use on the command line as any other, options and all. And being Ansible means I can run it here have something actually done on remote systems. Users who wish to see what’s happening, can use -v on the program to show the typical playbook output.

Paternoster was created by Michael “luto” Lutonsky to supplant a bunch of shell scripts at Uberspace, the hoster which also hosts this site. (I could rave about them but the simple fact that I’ve never complained about them speaks volumes!) Uberspace sponsored the development of the open code, and one day I will rave about them.

Further reading