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.
Files changed (184) hide show
  1. {pyinfra-3.0.2 → pyinfra-3.1.1}/CHANGELOG.md +41 -0
  2. {pyinfra-3.0.2/pyinfra.egg-info → pyinfra-3.1.1}/PKG-INFO +1 -1
  3. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/arguments.py +1 -1
  4. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/deploy.py +8 -0
  5. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/host.py +7 -3
  6. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/util.py +4 -2
  7. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/chroot.py +1 -1
  8. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/local.py +1 -1
  9. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/ssh.py +4 -1
  10. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/terraform.py +30 -20
  11. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/apt.py +2 -2
  12. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/files.py +8 -1
  13. pyinfra-3.1.1/pyinfra/facts/flatpak.py +70 -0
  14. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/hardware.py +1 -0
  15. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/selinux.py +3 -1
  16. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/systemd.py +4 -1
  17. pyinfra-3.1.1/pyinfra/facts/zfs.py +57 -0
  18. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/apt.py +2 -1
  19. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/docker.py +4 -4
  20. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/files.py +11 -4
  21. pyinfra-3.1.1/pyinfra/operations/flatpak.py +79 -0
  22. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/git.py +25 -2
  23. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/mysql.py +5 -5
  24. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/postgres.py +3 -3
  25. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/server.py +10 -10
  26. pyinfra-3.1.1/pyinfra/operations/zfs.py +175 -0
  27. {pyinfra-3.0.2 → pyinfra-3.1.1/pyinfra.egg-info}/PKG-INFO +1 -1
  28. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/SOURCES.txt +4 -0
  29. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/inventory.py +34 -2
  30. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/prints.py +16 -0
  31. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/util.py +2 -2
  32. {pyinfra-3.0.2 → pyinfra-3.1.1}/setup.cfg +1 -2
  33. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_inventory.py +21 -0
  34. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_exceptions.py +2 -2
  35. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_util.py +2 -4
  36. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_terraform.py +6 -6
  37. {pyinfra-3.0.2 → pyinfra-3.1.1}/LICENSE.md +0 -0
  38. {pyinfra-3.0.2 → pyinfra-3.1.1}/MANIFEST.in +0 -0
  39. {pyinfra-3.0.2 → pyinfra-3.1.1}/README.md +0 -0
  40. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/__init__.py +0 -0
  41. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/__main__.py +0 -0
  42. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/__init__.py +0 -0
  43. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/arguments_typed.py +0 -0
  44. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/command.py +0 -0
  45. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/config.py +0 -0
  46. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/connect.py +0 -0
  47. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/connectors.py +0 -0
  48. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/exceptions.py +0 -0
  49. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/facts.py +0 -0
  50. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/inventory.py +0 -0
  51. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/operation.py +0 -0
  52. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/operations.py +0 -0
  53. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/api/state.py +0 -0
  54. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/__init__.py +0 -0
  55. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/base.py +0 -0
  56. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/docker.py +0 -0
  57. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/dockerssh.py +0 -0
  58. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/ssh_util.py +0 -0
  59. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/sshuserclient/__init__.py +0 -0
  60. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/sshuserclient/client.py +0 -0
  61. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/sshuserclient/config.py +0 -0
  62. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/util.py +0 -0
  63. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/connectors/vagrant.py +0 -0
  64. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/context.py +0 -0
  65. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/__init__.py +0 -0
  66. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/apk.py +0 -0
  67. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/brew.py +0 -0
  68. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/bsdinit.py +0 -0
  69. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/cargo.py +0 -0
  70. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/choco.py +0 -0
  71. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/deb.py +0 -0
  72. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/dnf.py +0 -0
  73. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/docker.py +0 -0
  74. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/gem.py +0 -0
  75. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/git.py +0 -0
  76. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/gpg.py +0 -0
  77. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/iptables.py +0 -0
  78. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/launchd.py +0 -0
  79. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/lxd.py +0 -0
  80. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/mysql.py +0 -0
  81. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/npm.py +0 -0
  82. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/openrc.py +0 -0
  83. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pacman.py +0 -0
  84. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pip.py +0 -0
  85. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pkg.py +0 -0
  86. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/pkgin.py +0 -0
  87. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/postgres.py +0 -0
  88. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/postgresql.py +0 -0
  89. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/rpm.py +0 -0
  90. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/runit.py +0 -0
  91. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/server.py +0 -0
  92. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/snap.py +0 -0
  93. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/sysvinit.py +0 -0
  94. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/upstart.py +0 -0
  95. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/__init__.py +0 -0
  96. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/databases.py +0 -0
  97. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/packaging.py +0 -0
  98. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/util/win_files.py +0 -0
  99. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/vzctl.py +0 -0
  100. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/xbps.py +0 -0
  101. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/yum.py +0 -0
  102. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/facts/zypper.py +0 -0
  103. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/local.py +0 -0
  104. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/__init__.py +0 -0
  105. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/apk.py +0 -0
  106. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/brew.py +0 -0
  107. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/bsdinit.py +0 -0
  108. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/cargo.py +0 -0
  109. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/choco.py +0 -0
  110. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/dnf.py +0 -0
  111. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/gem.py +0 -0
  112. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/iptables.py +0 -0
  113. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/launchd.py +0 -0
  114. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/lxd.py +0 -0
  115. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/npm.py +0 -0
  116. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/openrc.py +0 -0
  117. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pacman.py +0 -0
  118. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pip.py +0 -0
  119. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pkg.py +0 -0
  120. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/pkgin.py +0 -0
  121. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/postgresql.py +0 -0
  122. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/puppet.py +0 -0
  123. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/python.py +0 -0
  124. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/runit.py +0 -0
  125. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/selinux.py +0 -0
  126. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/snap.py +0 -0
  127. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/ssh.py +0 -0
  128. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/systemd.py +0 -0
  129. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/sysvinit.py +0 -0
  130. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/upstart.py +0 -0
  131. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/__init__.py +0 -0
  132. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/docker.py +0 -0
  133. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/files.py +0 -0
  134. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/packaging.py +0 -0
  135. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/util/service.py +0 -0
  136. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/vzctl.py +0 -0
  137. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/xbps.py +0 -0
  138. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/yum.py +0 -0
  139. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/operations/zypper.py +0 -0
  140. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/progress.py +0 -0
  141. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/py.typed +0 -0
  142. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra/version.py +0 -0
  143. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/dependency_links.txt +0 -0
  144. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/entry_points.txt +0 -0
  145. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/requires.txt +0 -0
  146. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra.egg-info/top_level.txt +0 -0
  147. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/__init__.py +0 -0
  148. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/__main__.py +0 -0
  149. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/commands.py +0 -0
  150. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/exceptions.py +0 -0
  151. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/log.py +0 -0
  152. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/main.py +0 -0
  153. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyinfra_cli/virtualenv.py +0 -0
  154. {pyinfra-3.0.2 → pyinfra-3.1.1}/pyproject.toml +0 -0
  155. {pyinfra-3.0.2 → pyinfra-3.1.1}/setup.py +0 -0
  156. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/__init__.py +0 -0
  157. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api.py +0 -0
  158. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_arguments.py +0 -0
  159. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_command.py +0 -0
  160. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_config.py +0 -0
  161. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_deploys.py +0 -0
  162. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_facts.py +0 -0
  163. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_host.py +0 -0
  164. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_operations.py +0 -0
  165. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_api/test_api_util.py +0 -0
  166. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/__init__.py +0 -0
  167. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli.py +0 -0
  168. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_deploy.py +0 -0
  169. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_cli_inventory.py +0 -0
  170. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/test_context_objects.py +0 -0
  171. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_cli/util.py +0 -0
  172. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/__init__.py +0 -0
  173. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_chroot.py +0 -0
  174. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_docker.py +0 -0
  175. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_dockerssh.py +0 -0
  176. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_local.py +0 -0
  177. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_ssh.py +0 -0
  178. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_sshuserclient.py +0 -0
  179. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_util.py +0 -0
  180. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_connectors/test_vagrant.py +0 -0
  181. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_facts.py +0 -0
  182. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_global_arguments.py +0 -0
  183. {pyinfra-3.0.2 → pyinfra-3.1.1}/tests/test_operations.py +0 -0
  184. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyinfra
3
- Version: 3.0.2
3
+ Version: 3.1.1
4
4
  Summary: pyinfra automates/provisions/manages/deploys infrastructure.
5
5
  Home-page: https://pyinfra.com
6
6
  Author: Nick / Fizzadar
@@ -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
- return data[key]
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
- return disconnect_func()
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, encoding="utf-8") as temp_f:
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, encoding="utf-8") as temp_f:
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 alpha!")
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
- name = ""
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
- for ssh_target in tf_output_value:
106
- if isinstance(ssh_target, dict):
107
- name = ssh_target.pop("name", ssh_target.get("ssh_hostname"))
108
- if name is None:
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
- else:
111
+ for group_name, hosts in tf_output_value.items():
112
+ if not isinstance(hosts, list):
119
113
  raise InventoryError(
120
- "Invalid Terraform list item, should be `dict` or `str` got "
121
- f"`{type(ssh_target).__name__}`",
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+([a-z-]+)\s+([a-z-\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": set(matches.group(5).split()),
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
- QuoteString(path),
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:]]
@@ -109,6 +109,7 @@ class NetworkDevices(FactBase):
109
109
  ``ipv6_addresses`` facts for easier-to-use shortcuts to get device addresses.
110
110
 
111
111
  .. code:: python
112
+
112
113
  "enp1s0": {
113
114
  "ether": "12:34:56:78:9A:BC",
114
115
  "mtu": 1500,
@@ -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`. If there is no mapping, it returns ``{}``
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
- https://wiki.debian.org/DebianRepository/UseThirdParty
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 DockerContainers, DockerNetworks, DockerVolumes
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 = [c for c in host.get_fact(DockerContainers) if container in c["Name"]]
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 = [v for v in host.get_fact(DockerVolumes) if v["Name"] == 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 = [n for n in host.get_fact(DockerNetworks) if n["Name"] == 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 *), remember to escape
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 *.pyc)
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)