pyinfra 3.0.2__tar.gz → 3.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyinfra-3.0.2 → pyinfra-3.1.1}/CHANGELOG.md +41 -0
- {pyinfra-3.0.2/pyinfra.egg-info → pyinfra-3.1.1}/PKG-INFO +1 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/arguments.py +1 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/deploy.py +8 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/host.py +7 -3
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/util.py +4 -2
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/chroot.py +1 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/local.py +1 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/ssh.py +4 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/terraform.py +30 -20
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/apt.py +2 -2
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/files.py +8 -1
- pyinfra-3.1.1/pyinfra/facts/flatpak.py +70 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/hardware.py +1 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/selinux.py +3 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/systemd.py +4 -1
- pyinfra-3.1.1/pyinfra/facts/zfs.py +57 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/apt.py +2 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/docker.py +4 -4
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/files.py +11 -4
- pyinfra-3.1.1/pyinfra/operations/flatpak.py +79 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/git.py +25 -2
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/mysql.py +5 -5
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/postgres.py +3 -3
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/server.py +10 -10
- pyinfra-3.1.1/pyinfra/operations/zfs.py +175 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1/pyinfra.egg-info}/PKG-INFO +1 -1
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/SOURCES.txt +4 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/inventory.py +34 -2
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/prints.py +16 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/util.py +2 -2
- {pyinfra-3.0.2 → pyinfra-3.1.1}/setup.cfg +1 -2
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_inventory.py +21 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_exceptions.py +2 -2
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_util.py +2 -4
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_terraform.py +6 -6
- {pyinfra-3.0.2 → pyinfra-3.1.1}/LICENSE.md +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/MANIFEST.in +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/README.md +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/__main__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/arguments_typed.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/command.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/config.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/connect.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/connectors.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/exceptions.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/facts.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/inventory.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/operation.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/operations.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/state.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/base.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/docker.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/dockerssh.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/ssh_util.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/sshuserclient/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/sshuserclient/client.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/sshuserclient/config.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/util.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/vagrant.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/context.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/apk.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/brew.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/bsdinit.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/cargo.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/choco.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/deb.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/dnf.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/docker.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/gem.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/git.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/gpg.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/iptables.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/launchd.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/lxd.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/mysql.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/npm.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/openrc.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pacman.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pip.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pkg.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pkgin.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/postgres.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/postgresql.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/rpm.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/runit.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/server.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/snap.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/sysvinit.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/upstart.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/databases.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/packaging.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/win_files.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/vzctl.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/xbps.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/yum.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/zypper.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/local.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/apk.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/brew.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/bsdinit.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/cargo.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/choco.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/dnf.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/gem.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/iptables.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/launchd.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/lxd.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/npm.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/openrc.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pacman.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pip.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pkg.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pkgin.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/postgresql.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/puppet.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/python.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/runit.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/selinux.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/snap.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/ssh.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/systemd.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/sysvinit.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/upstart.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/docker.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/files.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/packaging.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/service.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/vzctl.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/xbps.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/yum.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/zypper.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/progress.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/py.typed +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/version.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/dependency_links.txt +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/entry_points.txt +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/requires.txt +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/top_level.txt +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/__main__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/commands.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/exceptions.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/log.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/main.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/virtualenv.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/pyproject.toml +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/setup.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_arguments.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_command.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_config.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_deploys.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_facts.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_host.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_operations.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_util.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_deploy.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_inventory.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_context_objects.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/util.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/__init__.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_chroot.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_docker.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_dockerssh.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_local.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_ssh.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_sshuserclient.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_util.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_vagrant.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_facts.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_global_arguments.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_operations.py +0 -0
- {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_operations_utils.py +0 -0
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
# v3.1.1
|
|
2
|
+
|
|
3
|
+
- Improve errors with 2.x style `@decorator` (vs `@decorator()`) functions
|
|
4
|
+
- Document adding custom connectors (@simonhammes)
|
|
5
|
+
- Add basic API example to docs (@pirate)
|
|
6
|
+
- Fix sphinx warnings (@simonhammes)
|
|
7
|
+
- Fix force & pull arguments in `git.worktree` operation
|
|
8
|
+
- Fix `server.reboot` reconnection (@wackou)
|
|
9
|
+
- Fix chroot/local connector non-utf file gets (@evoldstad)
|
|
10
|
+
- Fix `AptSources` fact to parse components in order & with digits (@rsfzi)
|
|
11
|
+
|
|
12
|
+
# v3.1
|
|
13
|
+
|
|
14
|
+
Here's pyinfra 3.1 - a release primarily driven by contributors new and old - a HUGE THANK YOU to all of you who dedicate time to work on pushing pyinfra forward. New stuff:
|
|
15
|
+
|
|
16
|
+
- Add `zfs` operations (`dataset`, `snapshot`, `volume`, `filesystem`) facts (`Pools`, `Datasets`, `Filesystems`, `Snapshots`, `Volumes`) (@taliaferro)
|
|
17
|
+
- Add `flatpak` operations (`packages`) and facts (`FlatpakPackage`, `FlatpakPackages`) (@JustScreaMy)
|
|
18
|
+
- Add `jinja_env_kwargs` argument to `files.template` operation (@DonDebonair)
|
|
19
|
+
- Add using dictionaries as `@terraform` output (map from group -> hosts)
|
|
20
|
+
- Add default `@terraform` output key - `pyinfra_inventory.value`, promote connector to beta
|
|
21
|
+
- Add support for multiple keys in each `server.authorized_keys` file (@matthijskooijman)
|
|
22
|
+
- Add print all dependency versions with `--support` flag (@kytta)
|
|
23
|
+
|
|
24
|
+
Fixes:
|
|
25
|
+
|
|
26
|
+
- Fix when `ssh_hostname` is set as override data, don't do inventory hostname check
|
|
27
|
+
- Fix `apt.AptSources` parsing special characters (@CondensedTea)
|
|
28
|
+
- Fix `server.reboot` connection detection (@bauen1 + @lemmi)
|
|
29
|
+
- Fix systemd flagging of sockets running (@bauen1)
|
|
30
|
+
- Fix mysql dump quoting (@simonhammes)
|
|
31
|
+
- Fix tilde expansion in files facts (@simonhammes)
|
|
32
|
+
- Fix host lookup check with SSH alias config (@simonhammes)
|
|
33
|
+
- Fix crontab comparison (@andrew-d)
|
|
34
|
+
|
|
35
|
+
Docs/internal tweaks:
|
|
36
|
+
|
|
37
|
+
- Improve operations documentation (@bauen1)
|
|
38
|
+
- Default to local machine if `user_name` set in systecmt (@bauen1)
|
|
39
|
+
- Improve efficiency of Docker operations (@apecnascimento)
|
|
40
|
+
- Shallow copy `host.data` data to mutation
|
|
41
|
+
|
|
1
42
|
# v3.0.2
|
|
2
43
|
|
|
3
44
|
- Fix `OperationMeta.did_change`: this is now a function as originally designed
|
|
@@ -137,7 +137,7 @@ shell_argument_meta: dict[str, ArgumentMeta] = {
|
|
|
137
137
|
),
|
|
138
138
|
"_chdir": ArgumentMeta(
|
|
139
139
|
"Directory to switch to before executing the command.",
|
|
140
|
-
default=lambda _:
|
|
140
|
+
default=lambda _: None,
|
|
141
141
|
),
|
|
142
142
|
"_env": ArgumentMeta(
|
|
143
143
|
"Dictionary of environment variables to set.",
|
|
@@ -60,6 +60,14 @@ def deploy(name: Optional[str] = None, data_defaults=None):
|
|
|
60
60
|
and wraps any operations called inside with any deploy-wide kwargs/data.
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
|
+
if name and not isinstance(name, str):
|
|
64
|
+
raise PyinfraError(
|
|
65
|
+
(
|
|
66
|
+
"The `deploy` decorator must be called, ie `@deploy()`, "
|
|
67
|
+
"see: https://docs.pyinfra.com/en/3.x/compatibility.html#upgrading-pyinfra-from-2-x-3-x" # noqa
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
63
71
|
def decorator(func: Callable[P, Any]) -> PyinfraOperation[P]:
|
|
64
72
|
func.deploy_name = name or func.__name__ # type: ignore[attr-defined]
|
|
65
73
|
if data_defaults:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from contextlib import contextmanager
|
|
4
|
+
from copy import copy
|
|
4
5
|
from typing import (
|
|
5
6
|
TYPE_CHECKING,
|
|
6
7
|
Any,
|
|
@@ -41,7 +42,6 @@ def extract_callable_datas(
|
|
|
41
42
|
# the data is stored on the state temporarily.
|
|
42
43
|
if callable(data):
|
|
43
44
|
data = data()
|
|
44
|
-
|
|
45
45
|
yield data
|
|
46
46
|
|
|
47
47
|
|
|
@@ -66,7 +66,10 @@ class HostData:
|
|
|
66
66
|
def __getattr__(self, key: str):
|
|
67
67
|
for data in extract_callable_datas(self.datas):
|
|
68
68
|
try:
|
|
69
|
-
|
|
69
|
+
# Take a shallow copy of the object here, we don't want modifications
|
|
70
|
+
# to host.data.<X> to stick, instead setting host.data.<Y> = is the
|
|
71
|
+
# correct way to achieve this (see __setattr__).
|
|
72
|
+
return copy(data[key])
|
|
70
73
|
except KeyError:
|
|
71
74
|
pass
|
|
72
75
|
|
|
@@ -403,12 +406,13 @@ class Host:
|
|
|
403
406
|
# Disconnect is an optional function for connectors if needed
|
|
404
407
|
disconnect_func = getattr(self.connector, "disconnect", None)
|
|
405
408
|
if disconnect_func:
|
|
406
|
-
|
|
409
|
+
disconnect_func()
|
|
407
410
|
|
|
408
411
|
# TODO: consider whether this should be here!
|
|
409
412
|
remove_any_sudo_askpass_file(self)
|
|
410
413
|
|
|
411
414
|
self.state.trigger_callbacks("host_disconnect", self)
|
|
415
|
+
self.connected = False
|
|
412
416
|
|
|
413
417
|
def run_shell_command(self, *args, **kwargs) -> tuple[bool, CommandOutput]:
|
|
414
418
|
"""
|
|
@@ -139,12 +139,13 @@ def get_operation_order_from_stack(state: "State"):
|
|
|
139
139
|
return line_numbers
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
def get_template(filename_or_io: str | IO):
|
|
142
|
+
def get_template(filename_or_io: str | IO, jinja_env_kwargs: dict[str, Any] | None = None):
|
|
143
143
|
"""
|
|
144
144
|
Gets a jinja2 ``Template`` object for the input filename or string, with caching
|
|
145
145
|
based on the filename of the template, or the SHA1 of the input string.
|
|
146
146
|
"""
|
|
147
|
-
|
|
147
|
+
if jinja_env_kwargs is None:
|
|
148
|
+
jinja_env_kwargs = {}
|
|
148
149
|
file_data = get_file_io(filename_or_io, mode="r")
|
|
149
150
|
cache_key = file_data.cache_key
|
|
150
151
|
|
|
@@ -158,6 +159,7 @@ def get_template(filename_or_io: str | IO):
|
|
|
158
159
|
undefined=StrictUndefined,
|
|
159
160
|
keep_trailing_newline=True,
|
|
160
161
|
loader=FileSystemLoader(getcwd()),
|
|
162
|
+
**jinja_env_kwargs,
|
|
161
163
|
).from_string(template_string)
|
|
162
164
|
|
|
163
165
|
if cache_key:
|
|
@@ -174,7 +174,7 @@ class ChrootConnector(BaseConnector):
|
|
|
174
174
|
)
|
|
175
175
|
|
|
176
176
|
# Load the temporary file and write it to our file or IO object
|
|
177
|
-
with open(temp_filename,
|
|
177
|
+
with open(temp_filename, "rb") as temp_f:
|
|
178
178
|
with get_file_io(filename_or_io, "wb") as file_io:
|
|
179
179
|
data = temp_f.read()
|
|
180
180
|
data_bytes: bytes
|
|
@@ -184,7 +184,7 @@ class LocalConnector(BaseConnector):
|
|
|
184
184
|
raise IOError(output.stderr)
|
|
185
185
|
|
|
186
186
|
# Load our file or IO object and write it to the temporary file
|
|
187
|
-
with open(temp_filename,
|
|
187
|
+
with open(temp_filename, "rb") as temp_f:
|
|
188
188
|
with get_file_io(filename_or_io, "wb") as file_io:
|
|
189
189
|
data_bytes: bytes
|
|
190
190
|
|
|
@@ -264,6 +264,9 @@ class SSHConnector(BaseConnector):
|
|
|
264
264
|
f"Host key for {e.hostname} does not match.",
|
|
265
265
|
)
|
|
266
266
|
|
|
267
|
+
def disconnect(self) -> None:
|
|
268
|
+
self.get_sftp_connection.cache.clear()
|
|
269
|
+
|
|
267
270
|
def run_shell_command(
|
|
268
271
|
self,
|
|
269
272
|
command: StringCommand,
|
|
@@ -606,7 +609,7 @@ class SSHConnector(BaseConnector):
|
|
|
606
609
|
rsync_flags=" ".join(flags),
|
|
607
610
|
ssh_flags=" ".join(ssh_flags),
|
|
608
611
|
remote_rsync_command=remote_rsync_command,
|
|
609
|
-
user=user,
|
|
612
|
+
user=user or "",
|
|
610
613
|
hostname=hostname,
|
|
611
614
|
src=src,
|
|
612
615
|
dest=dest,
|
|
@@ -10,16 +10,15 @@ from .base import BaseConnector
|
|
|
10
10
|
|
|
11
11
|
@memoize
|
|
12
12
|
def show_warning():
|
|
13
|
-
logger.warning("The @terraform connector is in
|
|
13
|
+
logger.warning("The @terraform connector is in beta!")
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _flatten_dict_gen(d, parent_key, sep):
|
|
17
17
|
for k, v in d.items():
|
|
18
18
|
new_key = parent_key + sep + k if parent_key else k
|
|
19
|
+
yield new_key, v
|
|
19
20
|
if isinstance(v, dict):
|
|
20
21
|
yield from _flatten_dict(v, new_key, sep=sep).items()
|
|
21
|
-
else:
|
|
22
|
-
yield new_key, v
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
def _flatten_dict(d: dict, parent_key: str = "", sep: str = "."):
|
|
@@ -82,7 +81,9 @@ class TerraformInventoryConnector(BaseConnector):
|
|
|
82
81
|
show_warning()
|
|
83
82
|
|
|
84
83
|
if not name:
|
|
85
|
-
|
|
84
|
+
# This is the default which allows one to create a Terraform output
|
|
85
|
+
# "pyinfra" and directly call: pyinfra @terraform ...
|
|
86
|
+
name = "pyinfra_inventory.value"
|
|
86
87
|
|
|
87
88
|
with progress_spinner({"fetch terraform output"}):
|
|
88
89
|
tf_output_raw = local.shell("terraform output -json")
|
|
@@ -96,27 +97,36 @@ class TerraformInventoryConnector(BaseConnector):
|
|
|
96
97
|
keys = "\n".join(f" - {k}" for k in tf_output.keys())
|
|
97
98
|
raise InventoryError(f"No Terraform output with key: `{name}`, valid keys:\n{keys}")
|
|
98
99
|
|
|
99
|
-
if not isinstance(tf_output_value, list):
|
|
100
|
+
if not isinstance(tf_output_value, (list, dict)):
|
|
100
101
|
raise InventoryError(
|
|
101
102
|
"Invalid Terraform output type, should be `list`, got "
|
|
102
103
|
f"`{type(tf_output_value).__name__}`",
|
|
103
104
|
)
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
raise InventoryError(
|
|
110
|
-
"Invalid Terraform list item, missing `name` or `ssh_hostname` keys",
|
|
111
|
-
)
|
|
112
|
-
yield f"@terraform/{name}", ssh_target, ["@terraform"]
|
|
113
|
-
|
|
114
|
-
elif isinstance(ssh_target, str):
|
|
115
|
-
data = {"ssh_hostname": ssh_target}
|
|
116
|
-
yield f"@terraform/{ssh_target}", data, ["@terraform"]
|
|
106
|
+
if isinstance(tf_output_value, list):
|
|
107
|
+
tf_output_value = {
|
|
108
|
+
"all": tf_output_value,
|
|
109
|
+
}
|
|
117
110
|
|
|
118
|
-
|
|
111
|
+
for group_name, hosts in tf_output_value.items():
|
|
112
|
+
if not isinstance(hosts, list):
|
|
119
113
|
raise InventoryError(
|
|
120
|
-
"Invalid Terraform
|
|
121
|
-
f"`{type(
|
|
114
|
+
"Invalid Terraform map value type, all values should be `list`, got "
|
|
115
|
+
f"`{type(hosts).__name__}`",
|
|
122
116
|
)
|
|
117
|
+
for host in hosts:
|
|
118
|
+
if isinstance(host, dict):
|
|
119
|
+
name = host.pop("name", host.get("ssh_hostname"))
|
|
120
|
+
if name is None:
|
|
121
|
+
raise InventoryError(
|
|
122
|
+
"Invalid Terraform list item, missing `name` or `ssh_hostname` keys",
|
|
123
|
+
)
|
|
124
|
+
yield f"@terraform/{name}", host, ["@terraform", group_name]
|
|
125
|
+
elif isinstance(host, str):
|
|
126
|
+
data = {"ssh_hostname": host}
|
|
127
|
+
yield f"@terraform/{host}", data, ["@terraform", group_name]
|
|
128
|
+
else:
|
|
129
|
+
raise InventoryError(
|
|
130
|
+
"Invalid Terraform list item, should be `dict` or `str` got "
|
|
131
|
+
f"`{type(host).__name__}`",
|
|
132
|
+
)
|
|
@@ -9,7 +9,7 @@ from .util import make_cat_files_command
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def parse_apt_repo(name):
|
|
12
|
-
regex = r"^(deb(?:-src)?)(?:\s+\[([^\]]+)\])?\s+([^\s]+)\s+([
|
|
12
|
+
regex = r"^(deb(?:-src)?)(?:\s+\[([^\]]+)\])?\s+([^\s]+)\s+([^\s]+)\s+([a-z-\s\d]*)$"
|
|
13
13
|
|
|
14
14
|
matches = re.match(regex, name)
|
|
15
15
|
|
|
@@ -32,7 +32,7 @@ def parse_apt_repo(name):
|
|
|
32
32
|
"type": matches.group(1),
|
|
33
33
|
"url": matches.group(3),
|
|
34
34
|
"distribution": matches.group(4),
|
|
35
|
-
"components":
|
|
35
|
+
"components": list(matches.group(5).split()),
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
|
|
@@ -5,6 +5,7 @@ The files facts provide information about the filesystem and it's contents on th
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
+
import shlex
|
|
8
9
|
import stat
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
|
@@ -109,13 +110,19 @@ class File(FactBase[Union[FileDict, Literal[False], None]]):
|
|
|
109
110
|
type = "file"
|
|
110
111
|
|
|
111
112
|
def command(self, path):
|
|
113
|
+
if path.startswith("~/"):
|
|
114
|
+
# Do not quote leading tilde to ensure that it gets properly expanded by the shell
|
|
115
|
+
path = f"~/{shlex.quote(path[2:])}"
|
|
116
|
+
else:
|
|
117
|
+
path = QuoteString(path)
|
|
118
|
+
|
|
112
119
|
return make_formatted_string_command(
|
|
113
120
|
(
|
|
114
121
|
# only stat if the path exists (file or symlink)
|
|
115
122
|
"! (test -e {0} || test -L {0} ) || "
|
|
116
123
|
"( {linux_stat_command} {0} 2> /dev/null || {bsd_stat_command} {0} )"
|
|
117
124
|
),
|
|
118
|
-
|
|
125
|
+
path,
|
|
119
126
|
linux_stat_command=LINUX_STAT_COMMAND,
|
|
120
127
|
bsd_stat_command=BSD_STAT_COMMAND,
|
|
121
128
|
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from pyinfra.api import FactBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FlatpakBaseFact(FactBase):
|
|
9
|
+
abstract = True
|
|
10
|
+
|
|
11
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
12
|
+
return "flatpak"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FlatpakPackage(FlatpakBaseFact):
|
|
16
|
+
"""
|
|
17
|
+
Returns information for an installed flatpak package
|
|
18
|
+
|
|
19
|
+
.. code:: python
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
"id": "org.signal.Signal",
|
|
23
|
+
"ref": "app/org.signal.Signal/x86_64/stable",
|
|
24
|
+
"version": "7.12.0"
|
|
25
|
+
}
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
default = dict
|
|
29
|
+
_regexes = {
|
|
30
|
+
"id": "^[ ]+ID:[ ]+(.*)$",
|
|
31
|
+
"ref": r"^[ ]+Ref:[ ]+(.*)$",
|
|
32
|
+
"version": r"^[ ]+Version:[ ]+([\w\d.-]+).*$",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def command(self, package):
|
|
36
|
+
return f"flatpak info {package}"
|
|
37
|
+
|
|
38
|
+
def process(self, output):
|
|
39
|
+
data = {}
|
|
40
|
+
for line in output:
|
|
41
|
+
for regex_name, regex in self._regexes.items():
|
|
42
|
+
matches = re.match(regex, line)
|
|
43
|
+
if matches:
|
|
44
|
+
data[regex_name] = matches.group(1)
|
|
45
|
+
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class FlatpakPackages(FlatpakBaseFact):
|
|
50
|
+
"""
|
|
51
|
+
Returns a list of installed flatpak packages:
|
|
52
|
+
|
|
53
|
+
.. code:: python
|
|
54
|
+
|
|
55
|
+
[
|
|
56
|
+
"org.gnome.Platform",
|
|
57
|
+
"org.kde.Platform",
|
|
58
|
+
"org.kde.Sdk",
|
|
59
|
+
"org.libreoffice.LibreOffice",
|
|
60
|
+
"org.videolan.VLC"
|
|
61
|
+
]
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
default = list
|
|
65
|
+
|
|
66
|
+
def command(self):
|
|
67
|
+
return "flatpak list --columns=application"
|
|
68
|
+
|
|
69
|
+
def process(self, output):
|
|
70
|
+
return [flatpak for flatpak in output[1:]]
|
|
@@ -58,7 +58,8 @@ class FileContext(FactBase):
|
|
|
58
58
|
class FileContextMapping(FactBase):
|
|
59
59
|
"""
|
|
60
60
|
Returns structured SELinux file context data for the specified target path prefix
|
|
61
|
-
using the same format as :ref:`selinux.FileContext`.
|
|
61
|
+
using the same format as :ref:`facts:selinux.FileContext`.
|
|
62
|
+
If there is no mapping, it returns ``{}``
|
|
62
63
|
Note: This fact requires root privileges.
|
|
63
64
|
"""
|
|
64
65
|
|
|
@@ -85,6 +86,7 @@ class SEPorts(FactBase):
|
|
|
85
86
|
Note: This fact requires root privileges.
|
|
86
87
|
|
|
87
88
|
.. code:: python
|
|
89
|
+
|
|
88
90
|
{
|
|
89
91
|
"tcp": { 22: "ssh_port_t", ...},
|
|
90
92
|
"udp": { ...}
|
|
@@ -32,6 +32,9 @@ def _make_systemctl_cmd(user_mode=False, machine=None, user_name=None):
|
|
|
32
32
|
systemctl_cmd.append("--machine={1}@{0}".format(machine, user_name))
|
|
33
33
|
else:
|
|
34
34
|
systemctl_cmd.append("--machine={0}".format(machine))
|
|
35
|
+
elif user_name is not None:
|
|
36
|
+
# If only the user is given, assume that the connection should be made to the local machine
|
|
37
|
+
systemctl_cmd.append("--machine={0}@.host".format(user_name))
|
|
35
38
|
|
|
36
39
|
return StringCommand(*systemctl_cmd)
|
|
37
40
|
|
|
@@ -58,7 +61,7 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
|
|
|
58
61
|
default = dict
|
|
59
62
|
|
|
60
63
|
state_key = "SubState"
|
|
61
|
-
state_values = ["running", "waiting", "exited"]
|
|
64
|
+
state_values = ["running", "waiting", "exited", "listening"]
|
|
62
65
|
|
|
63
66
|
def command(
|
|
64
67
|
self,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage ZFS filesystems.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pyinfra.api import FactBase, ShortFactBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _process_zfs_props_table(output):
|
|
9
|
+
datasets: dict = {}
|
|
10
|
+
for line in output:
|
|
11
|
+
dataset, property, value, source = tuple(line.split("\t"))
|
|
12
|
+
if dataset not in datasets:
|
|
13
|
+
datasets[dataset] = {}
|
|
14
|
+
datasets[dataset][property] = value
|
|
15
|
+
return datasets
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Pools(FactBase):
|
|
19
|
+
def command(self):
|
|
20
|
+
return "zpool get -H all"
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def process(output):
|
|
24
|
+
return _process_zfs_props_table(output)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Datasets(FactBase):
|
|
28
|
+
def command(self):
|
|
29
|
+
return "zfs get -H all"
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def process(output):
|
|
33
|
+
return _process_zfs_props_table(output)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Filesystems(ShortFactBase):
|
|
37
|
+
fact = Datasets
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def process_data(data):
|
|
41
|
+
return {name: props for name, props in data.items() if props.get("type") == "filesystem"}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Snapshots(ShortFactBase):
|
|
45
|
+
fact = Datasets
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def process_data(data):
|
|
49
|
+
return {name: props for name, props in data.items() if props.get("type") == "snapshot"}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Volumes(ShortFactBase):
|
|
53
|
+
fact = Datasets
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def process_data(data):
|
|
57
|
+
return {name: props for name, props in data.items() if props.get("type") == "volume"}
|
|
@@ -53,7 +53,8 @@ def key(src: str | None = None, keyserver: str | None = None, keyid: str | list[
|
|
|
53
53
|
.. warning::
|
|
54
54
|
``apt-key`` is deprecated in Debian, it is recommended NOT to use this
|
|
55
55
|
operation and instead follow the instructions here:
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
https://wiki.debian.org/DebianRepository/UseThirdParty
|
|
57
58
|
|
|
58
59
|
**Examples:**
|
|
59
60
|
|
|
@@ -4,7 +4,7 @@ Manager Docker Containers, Volumes and Networks
|
|
|
4
4
|
|
|
5
5
|
from pyinfra import host
|
|
6
6
|
from pyinfra.api import operation
|
|
7
|
-
from pyinfra.facts.docker import
|
|
7
|
+
from pyinfra.facts.docker import DockerContainer, DockerNetwork, DockerVolume
|
|
8
8
|
|
|
9
9
|
from .util.docker import handle_docker
|
|
10
10
|
|
|
@@ -68,7 +68,7 @@ def container(
|
|
|
68
68
|
)
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
|
-
existent_container =
|
|
71
|
+
existent_container = host.get_fact(DockerContainer, object_id=container)
|
|
72
72
|
|
|
73
73
|
if force:
|
|
74
74
|
if existent_container:
|
|
@@ -183,7 +183,7 @@ def volume(volume, driver="", labels=None, present=True):
|
|
|
183
183
|
)
|
|
184
184
|
"""
|
|
185
185
|
|
|
186
|
-
existent_volume =
|
|
186
|
+
existent_volume = host.get_fact(DockerVolume, object_id=volume)
|
|
187
187
|
|
|
188
188
|
if present:
|
|
189
189
|
|
|
@@ -257,7 +257,7 @@ def network(
|
|
|
257
257
|
present=True,
|
|
258
258
|
)
|
|
259
259
|
"""
|
|
260
|
-
existent_network =
|
|
260
|
+
existent_network = host.get_fact(DockerNetwork, object_id=network)
|
|
261
261
|
|
|
262
262
|
if present:
|
|
263
263
|
if existent_network:
|
|
@@ -256,7 +256,7 @@ def line(
|
|
|
256
256
|
change bits of lines, see ``files.replace``.
|
|
257
257
|
|
|
258
258
|
Regex line escaping:
|
|
259
|
-
If matching special characters (eg a crontab line containing
|
|
259
|
+
If matching special characters (eg a crontab line containing ``*``), remember to escape
|
|
260
260
|
it first using Python's ``re.escape``.
|
|
261
261
|
|
|
262
262
|
Backup:
|
|
@@ -523,7 +523,7 @@ def sync(
|
|
|
523
523
|
+ mode: permissions of the files
|
|
524
524
|
+ dir_mode: permissions of the directories
|
|
525
525
|
+ delete: delete remote files not present locally
|
|
526
|
-
+ exclude: string or list/tuple of strings to match & exclude files (eg
|
|
526
|
+
+ exclude: string or list/tuple of strings to match & exclude files (eg ``*.pyc``)
|
|
527
527
|
+ exclude_dir: string or list/tuple of strings to match & exclude directories (eg node_modules)
|
|
528
528
|
+ add_deploy_dir: interpret src as relative to deploy directory instead of current directory
|
|
529
529
|
|
|
@@ -915,7 +915,8 @@ def template(
|
|
|
915
915
|
user: str | None = None,
|
|
916
916
|
group: str | None = None,
|
|
917
917
|
mode: str | None = None,
|
|
918
|
-
create_remote_dir=True,
|
|
918
|
+
create_remote_dir: bool = True,
|
|
919
|
+
jinja_env_kwargs: dict[str, Any] | None = None,
|
|
919
920
|
**data,
|
|
920
921
|
):
|
|
921
922
|
'''
|
|
@@ -927,12 +928,18 @@ def template(
|
|
|
927
928
|
+ group: group to own the files
|
|
928
929
|
+ mode: permissions of the files
|
|
929
930
|
+ create_remote_dir: create the remote directory if it doesn't exist
|
|
931
|
+
+ jinja_env_kwargs: keyword arguments to be passed into the jinja Environment()
|
|
930
932
|
|
|
931
933
|
``create_remote_dir``:
|
|
932
934
|
If the remote directory does not exist it will be created using the same
|
|
933
935
|
user & group as passed to ``files.put``. The mode will *not* be copied over,
|
|
934
936
|
if this is required call ``files.directory`` separately.
|
|
935
937
|
|
|
938
|
+
``jinja_env_kwargs``:
|
|
939
|
+
To have more control over how jinja2 renders your template, you can pass
|
|
940
|
+
a dict with arguments that will be passed as keyword args to the jinja2
|
|
941
|
+
`Environment() <https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment>`_.
|
|
942
|
+
|
|
936
943
|
Notes:
|
|
937
944
|
Common convention is to store templates in a "templates" directory and
|
|
938
945
|
have a filename suffix with '.j2' (for jinja2).
|
|
@@ -1002,7 +1009,7 @@ def template(
|
|
|
1002
1009
|
|
|
1003
1010
|
# Render and make file-like it's output
|
|
1004
1011
|
try:
|
|
1005
|
-
output = get_template(src).render(data)
|
|
1012
|
+
output = get_template(src, jinja_env_kwargs).render(data)
|
|
1006
1013
|
except (TemplateRuntimeError, TemplateSyntaxError, UndefinedError) as e:
|
|
1007
1014
|
trace_frames = [
|
|
1008
1015
|
frame
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage flatpak packages. See https://www.flatpak.org/
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pyinfra import host
|
|
8
|
+
from pyinfra.api import operation
|
|
9
|
+
from pyinfra.facts.flatpak import FlatpakPackages
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@operation()
|
|
13
|
+
def packages(
|
|
14
|
+
packages: str | list[str] | None = None,
|
|
15
|
+
present=True,
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Install/remove a flatpak package
|
|
19
|
+
|
|
20
|
+
+ packages: List of packages
|
|
21
|
+
+ present: whether the package should be installed
|
|
22
|
+
|
|
23
|
+
**Examples:**
|
|
24
|
+
|
|
25
|
+
.. code:: python
|
|
26
|
+
|
|
27
|
+
# Install vlc flatpak
|
|
28
|
+
flatpak.package(
|
|
29
|
+
name="Install vlc",
|
|
30
|
+
packages="org.videolan.VLC",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Install multiple flatpaks
|
|
34
|
+
flatpak.package(
|
|
35
|
+
name="Install vlc and kodi",
|
|
36
|
+
packages=["org.videolan.VLC", "tv.kodi.Kodi"],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Remove vlc
|
|
40
|
+
flatpak.package(
|
|
41
|
+
name="Remove vlc",
|
|
42
|
+
packages="org.videolan.VLC",
|
|
43
|
+
present=False,
|
|
44
|
+
)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
if packages is None:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if isinstance(packages, str):
|
|
51
|
+
packages = [packages]
|
|
52
|
+
|
|
53
|
+
flatpak_packages = host.get_fact(FlatpakPackages)
|
|
54
|
+
|
|
55
|
+
install_packages = []
|
|
56
|
+
remove_packages = []
|
|
57
|
+
|
|
58
|
+
for package in packages:
|
|
59
|
+
# it's installed
|
|
60
|
+
if package in flatpak_packages:
|
|
61
|
+
if not present:
|
|
62
|
+
# we don't want it
|
|
63
|
+
remove_packages.append(package)
|
|
64
|
+
|
|
65
|
+
# it's not installed
|
|
66
|
+
if package not in flatpak_packages:
|
|
67
|
+
# we want it
|
|
68
|
+
if present:
|
|
69
|
+
install_packages.append(package)
|
|
70
|
+
|
|
71
|
+
# we don't want it
|
|
72
|
+
else:
|
|
73
|
+
host.noop(f"flatpak package {package} is not installed")
|
|
74
|
+
|
|
75
|
+
if install_packages:
|
|
76
|
+
yield " ".join(["flatpak", "install", "--noninteractive"] + install_packages)
|
|
77
|
+
|
|
78
|
+
if remove_packages:
|
|
79
|
+
yield " ".join(["flatpak", "uninstall", "--noninteractive"] + remove_packages)
|