Managing Systems and Applications with pyinfra
Using Host Data
You can also assign data to individual hosts. For example, you might assign a list of packages to install on each host. This is done by using a tuple with the hostname and a dictionary of data in the group list in the inventory file (inventory.py), as illustrated in Listing 1.
Listing 1
inventory.py with Host Data
01 servers = [
02 ("podpile.home", {"packages": ["tmux", "vim"]}),
03 ("code.example.com", {"packages": ["tmux"]})
04 ]
05 workstations = [
06 ("nov.home", {"packages": ["git", "tmux", "vim", "taskwarrior"]}),
07 ("tux.home", {"packages": ["git"]})
08 ]
With this host data in the inventory, the deploy file (deploy.py) can be made more generic, referring to the packages variable as host.data.packages, as in Listing 2. When you run this deploy, pyinfra installs a specific list of packages on each host.
Listing 2
deploy.py to Install Packages
01 from pyinfra import host 02 from pyinfra.operations import server 03 04 server.packages( 05 name="Install packages", packages=host.data.packages, _sudo=True 06 )
Using Group Data
Of course, you create groups of your hosts because they share common features. Therefore, pyinfra allows you to assign data to a group. For instance, if you want to install tmux and Vim on all your servers, you can define these in group_data/servers.py with
packages = ["tmux", "vim"]
And then, to install Git, tmux, Vim, and Taskwarrior on all your workstations, you would define them in group/data/workstations.py like this:
packages = ["git", "tmux", "vim", "taskwarrior"]
Within your inventory file, you can remove the host data, though you can still override the data for particular hosts. If you specify a key with host data in the inventory file, pyinfra uses this value. If not, it searches for the variable in the group data file from the host's group. If it isn't found there, it searches for the variable in a file group_data/all.py.
Installing Caddy in a Task
When your pyinfra deploy becomes more complex, it's best to break it into tasks. You can create a tasks directory and then put each task in its own Python file. I'll create a task to install the Caddy [6] web server, which will install the caddy package, upload a configuration file, and then restart and enable Caddy's systemd service. The task to accomplish this is shown in Listing 3.
Listing 3
Task to Install Caddy
01 from pyinfra import host 02 from pyinfra.operations import server, files, systemd 03 04 server.packages(name="Install Caddy", packages=["caddy"], _sudo=True) 05 06 caddyfile_installed = files.template( 07 name="Upload templated Caddyfile", 08 src="templates/Caddyfile.j2", 09 dest="/etc/caddy/Caddyfile", 10 message=host.data.welcome_message, 11 _sudo=True, 12 ) 13 14 systemd.service( 15 name="Restart Caddy", 16 service="caddy", 17 restarted=True, 18 enabled=True, 19 _sudo=True, 20 _if=caddyfile_installed.did_change, 21 )
After installing Caddy, this task uses the files.template operation to generate a Jinja2 [7] template and copy the result to the Caddyfile. Then the systemd.service operation restarts Caddy's systemd service, but only if the Caddyfile was changed.
The template, in templates/Caddyfile.j2, looks like
:80 {
respond "{{ message }}"
}The message placeholder is replaced by the message argument in the files.template operation, which points to host.data.welcome_message. So you need to set this host data in the inventory file:
webservers = [
("playground.home", {"packages": ["tmux"], "welcome_message": "Hello world!"})
]If your intent is to execute the Caddy task only on web servers, change your deploy.py according to Listing 4.
Listing 4
Import Specific Group Tasks in deploy.py
01 from pyinfra import host, local
02 from pyinfra.operations import server
03
04 server.packages(
05 name="Install packages", packages=host.data.packages, _sudo=True
06 )
07
08 if "webservers" in host.groups:
09 local.include("tasks/caddy.py")
Running tasks for specific hosts is also possible:
if host.name == "playground.home":
local.include("tasks/caddy.py")Buy this article as PDF
(incl. VAT)