pyinfra 3.0b1__tar.gz → 3.0b2__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.0b1 → pyinfra-3.0b2}/CHANGELOG.md +1 -1
- {pyinfra-3.0b1/pyinfra.egg-info → pyinfra-3.0b2}/PKG-INFO +66 -11
- {pyinfra-3.0b1 → pyinfra-3.0b2}/README.md +6 -8
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/arguments.py +9 -3
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/arguments_typed.py +8 -5
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/command.py +5 -3
- pyinfra-3.0b2/pyinfra/api/config.py +231 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/connectors.py +5 -2
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/facts.py +33 -32
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/host.py +5 -5
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/inventory.py +4 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/operation.py +22 -14
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/util.py +24 -16
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/base.py +3 -6
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/docker.py +2 -9
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/local.py +2 -2
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/ssh.py +2 -2
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/util.py +6 -7
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/vagrant.py +5 -5
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/context.py +1 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/apk.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/apt.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/brew.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/bsdinit.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/cargo.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/choco.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/deb.py +7 -2
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/dnf.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/docker.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/files.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/gem.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/gpg.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/hardware.py +30 -22
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/launchd.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/lxd.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/mysql.py +12 -6
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/npm.py +1 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/openrc.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pacman.py +6 -2
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pip.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pkg.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pkgin.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/postgres.py +6 -6
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/postgresql.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/rpm.py +12 -9
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/server.py +10 -13
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/snap.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/systemd.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/upstart.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/packaging.py +3 -2
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/vzctl.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/xbps.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/yum.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/zypper.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/apk.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/apt.py +16 -18
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/brew.py +10 -8
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/bsdinit.py +5 -3
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/cargo.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/choco.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/dnf.py +15 -19
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/files.py +81 -66
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/gem.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/git.py +18 -16
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/iptables.py +27 -25
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/launchd.py +5 -6
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/lxd.py +7 -4
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/mysql.py +57 -53
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/npm.py +8 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/openrc.py +5 -3
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pacman.py +4 -5
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pip.py +11 -9
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pkg.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pkgin.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/postgres.py +39 -37
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/postgresql.py +2 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/puppet.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/python.py +7 -3
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/selinux.py +42 -16
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/server.py +48 -43
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/snap.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/ssh.py +12 -10
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/systemd.py +8 -6
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/sysvinit.py +6 -4
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/upstart.py +5 -3
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/files.py +24 -16
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/packaging.py +53 -37
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/service.py +18 -13
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/vzctl.py +12 -10
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/xbps.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/yum.py +14 -18
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/zypper.py +8 -9
- pyinfra-3.0b2/pyinfra/version.py +9 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2/pyinfra.egg-info}/PKG-INFO +66 -11
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/SOURCES.txt +0 -3
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/requires.txt +20 -16
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/exceptions.py +0 -5
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/inventory.py +38 -19
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/prints.py +15 -11
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/util.py +3 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyproject.toml +1 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/setup.cfg +1 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/setup.py +14 -9
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_operations.py +1 -1
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_ssh.py +66 -13
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_vagrant.py +3 -3
- pyinfra-3.0b1/pyinfra/api/config.py +0 -129
- pyinfra-3.0b1/pyinfra/version.py +0 -6
- pyinfra-3.0b1/tests/__init__.py +0 -12
- pyinfra-3.0b1/tests/paramiko_util.py +0 -103
- pyinfra-3.0b1/tests/util.py +0 -407
- {pyinfra-3.0b1 → pyinfra-3.0b2}/LICENSE.md +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/MANIFEST.in +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/__main__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/connect.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/deploy.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/exceptions.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/operations.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/state.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/chroot.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/dockerssh.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/ssh_util.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/sshuserclient/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/sshuserclient/client.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/sshuserclient/config.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/terraform.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/git.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/iptables.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/selinux.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/sysvinit.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/databases.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/win_files.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/local.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/progress.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/py.typed +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/dependency_links.txt +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/entry_points.txt +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/top_level.txt +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/__main__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/commands.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/log.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/main.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/virtualenv.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_arguments.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_command.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_config.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_deploys.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_facts.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_host.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_inventory.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_util.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli_deploy.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli_exceptions.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli_util.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_context_objects.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/util.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/__init__.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_chroot.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_docker.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_dockerssh.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_local.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_sshuserclient.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_terraform.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_util.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_facts.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_global_arguments.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_operations.py +0 -0
- {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_operations_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyinfra
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.0b2
|
|
4
4
|
Summary: pyinfra automates/provisions/manages/deploys infrastructure.
|
|
5
5
|
Home-page: https://pyinfra.com
|
|
6
6
|
Author: Nick / Fizzadar
|
|
@@ -16,18 +16,75 @@ Classifier: Intended Audience :: Information Technology
|
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.8
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
24
|
Classifier: Topic :: System :: Systems Administration
|
|
24
25
|
Classifier: Topic :: System :: Installation/Setup
|
|
25
26
|
Classifier: Topic :: Utilities
|
|
27
|
+
Requires-Python: >=3.8
|
|
26
28
|
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE.md
|
|
30
|
+
Requires-Dist: gevent>=1.5
|
|
31
|
+
Requires-Dist: paramiko<4,>=2.7
|
|
32
|
+
Requires-Dist: click>2
|
|
33
|
+
Requires-Dist: jinja2<4,>2
|
|
34
|
+
Requires-Dist: python-dateutil<3,>2
|
|
35
|
+
Requires-Dist: setuptools
|
|
36
|
+
Requires-Dist: configparser
|
|
37
|
+
Requires-Dist: pywinrm
|
|
38
|
+
Requires-Dist: typeguard
|
|
39
|
+
Requires-Dist: distro<2,>=1.6
|
|
40
|
+
Requires-Dist: packaging>=16.1
|
|
41
|
+
Requires-Dist: graphlib_backport; python_version < "3.9"
|
|
42
|
+
Requires-Dist: typing-extensions; python_version < "3.11"
|
|
43
|
+
Requires-Dist: importlib_metadata>=3.6; python_version < "3.10"
|
|
27
44
|
Provides-Extra: test
|
|
45
|
+
Requires-Dist: pytest==8.2.1; extra == "test"
|
|
46
|
+
Requires-Dist: coverage==7.5.1; extra == "test"
|
|
47
|
+
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
48
|
+
Requires-Dist: black==24.4.2; extra == "test"
|
|
49
|
+
Requires-Dist: isort==5.13.2; extra == "test"
|
|
50
|
+
Requires-Dist: flake8==7.0.0; extra == "test"
|
|
51
|
+
Requires-Dist: flake8-black==0.3.6; extra == "test"
|
|
52
|
+
Requires-Dist: flake8-isort==6.1.1; extra == "test"
|
|
53
|
+
Requires-Dist: mypy; extra == "test"
|
|
54
|
+
Requires-Dist: types-cryptography; extra == "test"
|
|
55
|
+
Requires-Dist: types-paramiko; extra == "test"
|
|
56
|
+
Requires-Dist: types-python-dateutil; extra == "test"
|
|
57
|
+
Requires-Dist: types-PyYAML; extra == "test"
|
|
58
|
+
Requires-Dist: types-setuptools; extra == "test"
|
|
28
59
|
Provides-Extra: docs
|
|
60
|
+
Requires-Dist: pyinfra-guzzle_sphinx_theme==0.15; extra == "docs"
|
|
61
|
+
Requires-Dist: myst-parser==2.0.0; extra == "docs"
|
|
62
|
+
Requires-Dist: sphinx==6.2.1; extra == "docs"
|
|
29
63
|
Provides-Extra: dev
|
|
30
|
-
|
|
64
|
+
Requires-Dist: pytest==8.2.1; extra == "dev"
|
|
65
|
+
Requires-Dist: coverage==7.5.1; extra == "dev"
|
|
66
|
+
Requires-Dist: pytest-cov==5.0.0; extra == "dev"
|
|
67
|
+
Requires-Dist: black==24.4.2; extra == "dev"
|
|
68
|
+
Requires-Dist: isort==5.13.2; extra == "dev"
|
|
69
|
+
Requires-Dist: flake8==7.0.0; extra == "dev"
|
|
70
|
+
Requires-Dist: flake8-black==0.3.6; extra == "dev"
|
|
71
|
+
Requires-Dist: flake8-isort==6.1.1; extra == "dev"
|
|
72
|
+
Requires-Dist: mypy; extra == "dev"
|
|
73
|
+
Requires-Dist: types-cryptography; extra == "dev"
|
|
74
|
+
Requires-Dist: types-paramiko; extra == "dev"
|
|
75
|
+
Requires-Dist: types-python-dateutil; extra == "dev"
|
|
76
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
77
|
+
Requires-Dist: types-setuptools; extra == "dev"
|
|
78
|
+
Requires-Dist: pyinfra-guzzle_sphinx_theme==0.15; extra == "dev"
|
|
79
|
+
Requires-Dist: myst-parser==2.0.0; extra == "dev"
|
|
80
|
+
Requires-Dist: sphinx==6.2.1; extra == "dev"
|
|
81
|
+
Requires-Dist: wheel; extra == "dev"
|
|
82
|
+
Requires-Dist: twine; extra == "dev"
|
|
83
|
+
Requires-Dist: ipython; extra == "dev"
|
|
84
|
+
Requires-Dist: ipdb; extra == "dev"
|
|
85
|
+
Requires-Dist: ipdbplugin; extra == "dev"
|
|
86
|
+
Requires-Dist: flake8-spellcheck==0.12.1; extra == "dev"
|
|
87
|
+
Requires-Dist: redbaron; extra == "dev"
|
|
31
88
|
|
|
32
89
|
<p align="center">
|
|
33
90
|
<a href="https://pyinfra.com">
|
|
@@ -45,19 +102,17 @@ License-File: LICENSE.md
|
|
|
45
102
|
|
|
46
103
|
---
|
|
47
104
|
|
|
48
|
-
<
|
|
49
|
-
<a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> ⇒
|
|
105
|
+
<h3>
|
|
50
106
|
<a href="https://docs.pyinfra.com/page/getting-started.html"><strong>Getting Started</strong></a> •
|
|
51
|
-
<a href="https://
|
|
107
|
+
<a href="https://github.com/pyinfra-dev/pyinfra-examples"><strong>Examples Repo</strong></a> •
|
|
108
|
+
<a href="https://matrix.to/#/#pyinfra:matrix.org"><strong>Chat on Matrix</strong></a>
|
|
109
|
+
</h3>
|
|
110
|
+
<p>
|
|
111
|
+
<a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> •
|
|
52
112
|
<a href="https://docs.pyinfra.com/page/support.html"><strong>Help & Support</strong></a> •
|
|
53
113
|
<a href="https://docs.pyinfra.com/page/contributing.html"><strong>Contributing</strong></a>
|
|
54
114
|
</p>
|
|
55
115
|
|
|
56
|
-
<p>
|
|
57
|
-
Chat ⇒
|
|
58
|
-
<a href="https://matrix.to/#/#pyinfra:matrix.org"><strong><code>#pyinfra</code> on Matrix</strong></a>
|
|
59
|
-
</p>
|
|
60
|
-
|
|
61
116
|
---
|
|
62
117
|
|
|
63
118
|
Why pyinfra? Design features include:
|
|
@@ -14,19 +14,17 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
<
|
|
18
|
-
<a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> ⇒
|
|
17
|
+
<h3>
|
|
19
18
|
<a href="https://docs.pyinfra.com/page/getting-started.html"><strong>Getting Started</strong></a> •
|
|
20
|
-
<a href="https://
|
|
19
|
+
<a href="https://github.com/pyinfra-dev/pyinfra-examples"><strong>Examples Repo</strong></a> •
|
|
20
|
+
<a href="https://matrix.to/#/#pyinfra:matrix.org"><strong>Chat on Matrix</strong></a>
|
|
21
|
+
</h3>
|
|
22
|
+
<p>
|
|
23
|
+
<a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> •
|
|
21
24
|
<a href="https://docs.pyinfra.com/page/support.html"><strong>Help & Support</strong></a> •
|
|
22
25
|
<a href="https://docs.pyinfra.com/page/contributing.html"><strong>Contributing</strong></a>
|
|
23
26
|
</p>
|
|
24
27
|
|
|
25
|
-
<p>
|
|
26
|
-
Chat ⇒
|
|
27
|
-
<a href="https://matrix.to/#/#pyinfra:matrix.org"><strong><code>#pyinfra</code> on Matrix</strong></a>
|
|
28
|
-
</p>
|
|
29
|
-
|
|
30
28
|
---
|
|
31
29
|
|
|
32
30
|
Why pyinfra? Design features include:
|
|
@@ -9,6 +9,7 @@ from typing import (
|
|
|
9
9
|
List,
|
|
10
10
|
Mapping,
|
|
11
11
|
Optional,
|
|
12
|
+
Type,
|
|
12
13
|
TypeVar,
|
|
13
14
|
Union,
|
|
14
15
|
cast,
|
|
@@ -170,7 +171,7 @@ class MetaArguments(TypedDict):
|
|
|
170
171
|
name: str
|
|
171
172
|
_ignore_errors: bool
|
|
172
173
|
_continue_on_error: bool
|
|
173
|
-
_if: List[Callable[[], bool]]
|
|
174
|
+
_if: Union[List[Callable[[], bool]], Callable[[], bool], None]
|
|
174
175
|
|
|
175
176
|
|
|
176
177
|
meta_argument_meta: dict[str, ArgumentMeta] = {
|
|
@@ -191,7 +192,7 @@ meta_argument_meta: dict[str, ArgumentMeta] = {
|
|
|
191
192
|
default=lambda _: False,
|
|
192
193
|
),
|
|
193
194
|
"_if": ArgumentMeta(
|
|
194
|
-
"Only run this operation if these functions
|
|
195
|
+
"Only run this operation if these functions return True",
|
|
195
196
|
default=lambda _: [],
|
|
196
197
|
),
|
|
197
198
|
}
|
|
@@ -228,6 +229,11 @@ class AllArguments(ConnectorArguments, MetaArguments, ExecutionArguments):
|
|
|
228
229
|
pass
|
|
229
230
|
|
|
230
231
|
|
|
232
|
+
def all_global_arguments() -> List[tuple[str, Type]]:
|
|
233
|
+
"""Return all global arguments and their types."""
|
|
234
|
+
return list(get_type_hints(AllArguments).items())
|
|
235
|
+
|
|
236
|
+
|
|
231
237
|
all_argument_meta: dict[str, ArgumentMeta] = {
|
|
232
238
|
**auth_argument_meta,
|
|
233
239
|
**shell_argument_meta,
|
|
@@ -305,7 +311,7 @@ def pop_global_arguments(
|
|
|
305
311
|
arguments: dict[str, Any] = {}
|
|
306
312
|
found_keys: list[str] = []
|
|
307
313
|
|
|
308
|
-
for key, type_ in
|
|
314
|
+
for key, type_ in all_global_arguments():
|
|
309
315
|
if keys_to_check and key not in keys_to_check:
|
|
310
316
|
continue
|
|
311
317
|
|
|
@@ -32,6 +32,11 @@ class PyinfraOperation(Generic[P], Protocol):
|
|
|
32
32
|
def __call__(
|
|
33
33
|
self,
|
|
34
34
|
#
|
|
35
|
+
# op args
|
|
36
|
+
# needs to be first
|
|
37
|
+
#
|
|
38
|
+
*args: P.args,
|
|
39
|
+
#
|
|
35
40
|
# ConnectorArguments
|
|
36
41
|
#
|
|
37
42
|
# Auth
|
|
@@ -61,7 +66,7 @@ class PyinfraOperation(Generic[P], Protocol):
|
|
|
61
66
|
name: Optional[str] = None,
|
|
62
67
|
_ignore_errors: bool = False,
|
|
63
68
|
_continue_on_error: bool = False,
|
|
64
|
-
_if:
|
|
69
|
+
_if: Union[List[Callable[[], bool]], Callable[[], bool], None] = None,
|
|
65
70
|
#
|
|
66
71
|
# ExecutionArguments
|
|
67
72
|
#
|
|
@@ -69,9 +74,7 @@ class PyinfraOperation(Generic[P], Protocol):
|
|
|
69
74
|
_run_once: bool = False,
|
|
70
75
|
_serial: bool = False,
|
|
71
76
|
#
|
|
72
|
-
#
|
|
77
|
+
# op kwargs
|
|
73
78
|
#
|
|
74
|
-
*args: P.args,
|
|
75
79
|
**kwargs: P.kwargs,
|
|
76
|
-
) -> "OperationMeta":
|
|
77
|
-
...
|
|
80
|
+
) -> "OperationMeta": ...
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import shlex
|
|
2
4
|
from inspect import getfullargspec
|
|
3
5
|
from string import Formatter
|
|
4
|
-
from typing import TYPE_CHECKING, Callable, Union
|
|
6
|
+
from typing import IO, TYPE_CHECKING, Callable, Union
|
|
5
7
|
|
|
6
8
|
import gevent
|
|
7
9
|
from typing_extensions import Unpack
|
|
@@ -143,7 +145,7 @@ class StringCommand(PyinfraCommand):
|
|
|
143
145
|
class FileUploadCommand(PyinfraCommand):
|
|
144
146
|
def __init__(
|
|
145
147
|
self,
|
|
146
|
-
src: str,
|
|
148
|
+
src: str | IO,
|
|
147
149
|
dest: str,
|
|
148
150
|
remote_temp_filename=None,
|
|
149
151
|
**kwargs: Unpack[ConnectorArguments],
|
|
@@ -173,7 +175,7 @@ class FileDownloadCommand(PyinfraCommand):
|
|
|
173
175
|
def __init__(
|
|
174
176
|
self,
|
|
175
177
|
src: str,
|
|
176
|
-
dest: str,
|
|
178
|
+
dest: str | IO,
|
|
177
179
|
remote_temp_filename=None,
|
|
178
180
|
**kwargs: Unpack[ConnectorArguments],
|
|
179
181
|
):
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
try:
|
|
2
|
+
import importlib_metadata
|
|
3
|
+
except ImportError:
|
|
4
|
+
import importlib.metadata as importlib_metadata # type: ignore[no-redef]
|
|
5
|
+
from os import path
|
|
6
|
+
from typing import Iterable, Optional, Set
|
|
7
|
+
|
|
8
|
+
from packaging.markers import Marker
|
|
9
|
+
from packaging.requirements import Requirement
|
|
10
|
+
from packaging.specifiers import SpecifierSet
|
|
11
|
+
from packaging.version import Version
|
|
12
|
+
|
|
13
|
+
from pyinfra import __version__, state
|
|
14
|
+
|
|
15
|
+
from .exceptions import PyinfraError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConfigDefaults:
|
|
19
|
+
# % of hosts which have to fail for all operations to stop
|
|
20
|
+
FAIL_PERCENT: Optional[int] = None
|
|
21
|
+
# Seconds to timeout SSH connections
|
|
22
|
+
CONNECT_TIMEOUT: int = 10
|
|
23
|
+
# Temporary directory (on the remote side) to use for caching any files/downloads, the default
|
|
24
|
+
# None value first tries to load the hosts' temporary directory configured via "TMPDIR" env
|
|
25
|
+
# variable, falling back to DEFAULT_TEMP_DIR if not set.
|
|
26
|
+
TEMP_DIR: Optional[str] = None
|
|
27
|
+
DEFAULT_TEMP_DIR: str = "/tmp"
|
|
28
|
+
# Gevent pool size (defaults to #of target hosts)
|
|
29
|
+
PARALLEL: int = 0
|
|
30
|
+
# Specify the required pyinfra version (using PEP 440 setuptools specifier)
|
|
31
|
+
REQUIRE_PYINFRA_VERSION: Optional[str] = None
|
|
32
|
+
# Specify any required packages (either using PEP 440 or a requirements file)
|
|
33
|
+
# Note: this can also include pyinfra potentially replacing REQUIRE_PYINFRA_VERSION
|
|
34
|
+
REQUIRE_PACKAGES: Optional[str] = None
|
|
35
|
+
# All these can be overridden inside individual operation calls:
|
|
36
|
+
# Switch to this user (from ssh_user) using su before executing operations
|
|
37
|
+
SU_USER: Optional[str] = None
|
|
38
|
+
USE_SU_LOGIN: bool = False
|
|
39
|
+
SU_SHELL: bool = False
|
|
40
|
+
PRESERVE_SU_ENV: bool = False
|
|
41
|
+
# Use sudo and optional user
|
|
42
|
+
SUDO: bool = False
|
|
43
|
+
SUDO_USER: Optional[str] = None
|
|
44
|
+
PRESERVE_SUDO_ENV: bool = False
|
|
45
|
+
USE_SUDO_LOGIN: bool = False
|
|
46
|
+
SUDO_PASSWORD: Optional[str] = None
|
|
47
|
+
# Use doas and optional user
|
|
48
|
+
DOAS: bool = False
|
|
49
|
+
DOAS_USER: Optional[str] = None
|
|
50
|
+
# Only show errors but don't count as failure
|
|
51
|
+
IGNORE_ERRORS: bool = False
|
|
52
|
+
# Shell to use to execute commands
|
|
53
|
+
SHELL: str = "sh"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
config_defaults = {key: value for key, value in ConfigDefaults.__dict__.items() if key.isupper()}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def check_pyinfra_version(version: str):
|
|
60
|
+
if not version:
|
|
61
|
+
return
|
|
62
|
+
running_version = Version(__version__)
|
|
63
|
+
required_versions = SpecifierSet(version)
|
|
64
|
+
|
|
65
|
+
if running_version not in required_versions:
|
|
66
|
+
raise PyinfraError(
|
|
67
|
+
f"pyinfra version requirement not met (requires {version}, running {__version__})"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _check_requirements(requirements: Iterable[str]) -> Set[Requirement]:
|
|
72
|
+
"""
|
|
73
|
+
Check whether each of the given requirements and all their dependencies are
|
|
74
|
+
installed.
|
|
75
|
+
|
|
76
|
+
Or more precisely, this checks that each of the given *requirements* is
|
|
77
|
+
satisfied by some installed *distribution package*, and so on recursively
|
|
78
|
+
for each of the dependencies of those distribution packages. The terminology
|
|
79
|
+
here is as follows:
|
|
80
|
+
|
|
81
|
+
* A *distribution package* is essentially a thing that can be installed with
|
|
82
|
+
``pip``, from an sdist or wheel or Git repo or so on.
|
|
83
|
+
* A *requirement* is the expectation that a distribution package satisfying
|
|
84
|
+
some constraint is installed.
|
|
85
|
+
* A *dependency* is a requirement specified by a distribution package (as
|
|
86
|
+
opposed to the requirements passed in to this function).
|
|
87
|
+
|
|
88
|
+
So what this function does is start from the given requirements, for each
|
|
89
|
+
one check that it is satisfied by some installed distribution package, and
|
|
90
|
+
if so recursively perform the same check on all the dependencies of that
|
|
91
|
+
distribution package. In short, it's traversing the graph of package
|
|
92
|
+
requirements. It stops whenever it finds a requirement that is not satisfied
|
|
93
|
+
(i.e. a required package that is not installed), or when it runs out of
|
|
94
|
+
requirements to check.
|
|
95
|
+
|
|
96
|
+
.. note::
|
|
97
|
+
This is basically equivalent to ``pkg_resources.require()`` except that
|
|
98
|
+
when ``require()`` succeeds, it will return the list of distribution
|
|
99
|
+
packages that satisfy the given requirements and their dependencies, and
|
|
100
|
+
when it fails, it will raise an exception. This function just returns
|
|
101
|
+
the requirements which were not satisfied instead.
|
|
102
|
+
|
|
103
|
+
:param requirements: The requirements to check for in the set of installed
|
|
104
|
+
packages (along with their dependencies).
|
|
105
|
+
:return: The set of requirements that were not satisfied, which will be
|
|
106
|
+
an empty set if all requirements (recursively) were satisfied.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
# Based on pkg_resources.require() from setuptools. The implementation of
|
|
110
|
+
# hbutils.system.check_reqs() from the hbutils package was also helpful in
|
|
111
|
+
# clarifying what this is supposed to do.
|
|
112
|
+
|
|
113
|
+
reqs_to_check: Set[Requirement] = set(Requirement(r) for r in requirements)
|
|
114
|
+
reqs_satisfied: Set[Requirement] = set()
|
|
115
|
+
reqs_not_satisfied: Set[Requirement] = set()
|
|
116
|
+
|
|
117
|
+
while reqs_to_check:
|
|
118
|
+
req = reqs_to_check.pop()
|
|
119
|
+
assert req not in reqs_satisfied and req not in reqs_not_satisfied
|
|
120
|
+
|
|
121
|
+
# Check for an installed distribution package with the right name and version
|
|
122
|
+
try:
|
|
123
|
+
dist = importlib_metadata.distribution(req.name)
|
|
124
|
+
except importlib_metadata.PackageNotFoundError:
|
|
125
|
+
# No installed package with the right name
|
|
126
|
+
# This would raise a DistributionNotFound error from pkg_resources.require()
|
|
127
|
+
reqs_not_satisfied.add(req)
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
if dist.version not in req.specifier:
|
|
131
|
+
# There is a distribution with the right name but wrong version
|
|
132
|
+
# This would raise a VersionConflict error from pkg_resources.require()
|
|
133
|
+
reqs_not_satisfied.add(req)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
reqs_satisfied.add(req)
|
|
137
|
+
|
|
138
|
+
# If the distribution package has dependencies of its own, go through
|
|
139
|
+
# those dependencies and for each one add it to the set to be checked if
|
|
140
|
+
# - it's unconditional (no marker)
|
|
141
|
+
# - or it's conditional and the condition is satisfied (the marker
|
|
142
|
+
# evaluates to true) in the current environment
|
|
143
|
+
# Markers can check things like the Python version and system version
|
|
144
|
+
# etc., and/or they can check which extras of the distribution package
|
|
145
|
+
# were required. To facilitate checking extras we have to pass the extra
|
|
146
|
+
# in the environment when calling Marker.evaluate().
|
|
147
|
+
if dist.requires:
|
|
148
|
+
if req.extras:
|
|
149
|
+
extras_envs = [{"extra": extra} for extra in req.extras]
|
|
150
|
+
|
|
151
|
+
def evaluate_marker(marker: Marker) -> bool:
|
|
152
|
+
return any(map(marker.evaluate, extras_envs))
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
|
|
156
|
+
def evaluate_marker(marker: Marker) -> bool:
|
|
157
|
+
return marker.evaluate()
|
|
158
|
+
|
|
159
|
+
for dist_req_str in dist.requires:
|
|
160
|
+
dist_req = Requirement(dist_req_str)
|
|
161
|
+
if dist_req in reqs_satisfied or dist_req in reqs_not_satisfied:
|
|
162
|
+
continue
|
|
163
|
+
if (not dist_req.marker) or evaluate_marker(dist_req.marker):
|
|
164
|
+
reqs_to_check.add(dist_req)
|
|
165
|
+
|
|
166
|
+
return reqs_not_satisfied
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def check_require_packages(requirements_config):
|
|
170
|
+
if not requirements_config:
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
if isinstance(requirements_config, (list, tuple)):
|
|
174
|
+
requirements = requirements_config
|
|
175
|
+
else:
|
|
176
|
+
with open(path.join(state.cwd or "", requirements_config), encoding="utf-8") as f:
|
|
177
|
+
requirements = [line.split("#egg=")[-1] for line in f.read().splitlines()]
|
|
178
|
+
|
|
179
|
+
requirements_not_met = _check_requirements(requirements)
|
|
180
|
+
if requirements_not_met:
|
|
181
|
+
raise PyinfraError(
|
|
182
|
+
"Deploy requirements ({0}) not met: missing {1}".format(
|
|
183
|
+
requirements_config, ", ".join(str(r) for r in requirements_not_met)
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
config_checkers = {
|
|
189
|
+
"REQUIRE_PYINFRA_VERSION": check_pyinfra_version,
|
|
190
|
+
"REQUIRE_PACKAGES": check_require_packages,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Config(ConfigDefaults):
|
|
195
|
+
"""
|
|
196
|
+
The default/base configuration options for a pyinfra deploy.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def __init__(self, **kwargs):
|
|
200
|
+
# Always apply some env
|
|
201
|
+
env = kwargs.pop("ENV", {})
|
|
202
|
+
self.ENV = env
|
|
203
|
+
|
|
204
|
+
config = config_defaults.copy()
|
|
205
|
+
config.update(kwargs)
|
|
206
|
+
|
|
207
|
+
for key, value in config.items():
|
|
208
|
+
setattr(self, key, value)
|
|
209
|
+
|
|
210
|
+
def __setattr__(self, key, value):
|
|
211
|
+
super().__setattr__(key, value)
|
|
212
|
+
|
|
213
|
+
checker = config_checkers.get(key)
|
|
214
|
+
if checker:
|
|
215
|
+
checker(value)
|
|
216
|
+
|
|
217
|
+
def get_current_state(self):
|
|
218
|
+
return [(key, getattr(self, key)) for key in config_defaults.keys()]
|
|
219
|
+
|
|
220
|
+
def set_current_state(self, config_state):
|
|
221
|
+
for key, value in config_state:
|
|
222
|
+
setattr(self, key, value)
|
|
223
|
+
|
|
224
|
+
def lock_current_state(self):
|
|
225
|
+
self._locked_config = self.get_current_state()
|
|
226
|
+
|
|
227
|
+
def reset_locked_state(self):
|
|
228
|
+
self.set_current_state(self._locked_config)
|
|
229
|
+
|
|
230
|
+
def copy(self) -> "Config":
|
|
231
|
+
return Config(**dict(self.get_current_state()))
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
try:
|
|
2
|
+
from importlib_metadata import entry_points
|
|
3
|
+
except ImportError:
|
|
4
|
+
from importlib.metadata import entry_points # type: ignore[assignment]
|
|
2
5
|
|
|
3
6
|
|
|
4
7
|
def _load_connector(entrypoint):
|
|
@@ -8,7 +11,7 @@ def _load_connector(entrypoint):
|
|
|
8
11
|
def get_all_connectors():
|
|
9
12
|
return {
|
|
10
13
|
entrypoint.name: _load_connector(entrypoint)
|
|
11
|
-
for entrypoint in
|
|
14
|
+
for entrypoint in entry_points(group="pyinfra.connectors")
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
|
|
@@ -10,6 +10,7 @@ other host B while I operate on this host A).
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import inspect
|
|
13
14
|
import re
|
|
14
15
|
from inspect import getcallargs
|
|
15
16
|
from socket import error as socket_error, timeout as timeout_error
|
|
@@ -32,7 +33,7 @@ from paramiko import SSHException
|
|
|
32
33
|
|
|
33
34
|
from pyinfra import logger
|
|
34
35
|
from pyinfra.api import StringCommand
|
|
35
|
-
from pyinfra.api.arguments import pop_global_arguments
|
|
36
|
+
from pyinfra.api.arguments import all_global_arguments, pop_global_arguments
|
|
36
37
|
from pyinfra.api.util import (
|
|
37
38
|
get_kwargs_str,
|
|
38
39
|
log_error_or_warning,
|
|
@@ -76,6 +77,17 @@ class FactBase(Generic[T]):
|
|
|
76
77
|
module_name = cls.__module__.replace("pyinfra.facts.", "")
|
|
77
78
|
cls.name = f"{module_name}.{cls.__name__}"
|
|
78
79
|
|
|
80
|
+
# Check that fact's `command` method does not inadvertently take a global
|
|
81
|
+
# argument, most commonly `name`.
|
|
82
|
+
if hasattr(cls, "command") and callable(cls.command):
|
|
83
|
+
command_args = set(inspect.signature(cls.command).parameters.keys())
|
|
84
|
+
global_args = set([name for name, _ in all_global_arguments()])
|
|
85
|
+
command_global_args = command_args & global_args
|
|
86
|
+
|
|
87
|
+
if len(command_global_args) > 0:
|
|
88
|
+
names = ", ".join(command_global_args)
|
|
89
|
+
raise TypeError(f"{cls.name}'s arguments {names} are reserved for global arguments")
|
|
90
|
+
|
|
79
91
|
@staticmethod
|
|
80
92
|
def default() -> T:
|
|
81
93
|
"""
|
|
@@ -146,34 +158,25 @@ def _handle_fact_kwargs(state, host, cls, args, kwargs):
|
|
|
146
158
|
args = args or []
|
|
147
159
|
kwargs = kwargs or {}
|
|
148
160
|
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
# Start with a (shallow) copy of current operation kwargs if any
|
|
162
|
+
ctx_kwargs = (host.current_op_global_arguments or {}).copy()
|
|
163
|
+
# Update with the input kwargs (overrides)
|
|
164
|
+
ctx_kwargs.update(kwargs)
|
|
152
165
|
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
kwargs,
|
|
166
|
+
# Pop executor kwargs, pass remaining
|
|
167
|
+
global_kwargs, _ = pop_global_arguments(
|
|
168
|
+
ctx_kwargs,
|
|
157
169
|
state=state,
|
|
158
170
|
host=host,
|
|
159
|
-
keys_to_check=CONNECTOR_ARGUMENT_KEYS,
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
executor_kwargs = _get_executor_kwargs(
|
|
163
|
-
state,
|
|
164
|
-
host,
|
|
165
|
-
override_kwargs=override_kwargs, # type: ignore[arg-type]
|
|
166
|
-
override_kwarg_keys=override_kwarg_keys,
|
|
167
171
|
)
|
|
168
172
|
|
|
169
|
-
fact_kwargs = {}
|
|
173
|
+
fact_kwargs = {key: value for key, value in kwargs.items() if key not in global_kwargs}
|
|
170
174
|
|
|
171
|
-
if args or
|
|
172
|
-
assert not isinstance(cls.command, str)
|
|
175
|
+
if args or fact_kwargs:
|
|
173
176
|
# Merges args & kwargs into a single kwargs dictionary
|
|
174
|
-
fact_kwargs = getcallargs(cls().command, *args, **
|
|
177
|
+
fact_kwargs = getcallargs(cls().command, *args, **fact_kwargs)
|
|
175
178
|
|
|
176
|
-
return fact_kwargs,
|
|
179
|
+
return fact_kwargs, global_kwargs
|
|
177
180
|
|
|
178
181
|
|
|
179
182
|
def get_facts(state: "State", *args, **kwargs):
|
|
@@ -241,7 +244,7 @@ def _get_fact(
|
|
|
241
244
|
fact = cls()
|
|
242
245
|
name = fact.name
|
|
243
246
|
|
|
244
|
-
fact_kwargs,
|
|
247
|
+
fact_kwargs, global_kwargs = _handle_fact_kwargs(state, host, cls, args, kwargs)
|
|
245
248
|
|
|
246
249
|
kwargs_str = get_kwargs_str(fact_kwargs)
|
|
247
250
|
logger.debug(
|
|
@@ -257,15 +260,9 @@ def _get_fact(
|
|
|
257
260
|
raise_exceptions=True,
|
|
258
261
|
)
|
|
259
262
|
|
|
260
|
-
ignore_errors = (
|
|
261
|
-
host.current_op_global_arguments["_ignore_errors"]
|
|
262
|
-
if host.in_op and host.current_op_global_arguments
|
|
263
|
-
else state.config.IGNORE_ERRORS
|
|
264
|
-
)
|
|
265
|
-
|
|
266
263
|
# Facts can override the shell (winrm powershell vs cmd support)
|
|
267
264
|
if fact.shell_executable:
|
|
268
|
-
|
|
265
|
+
global_kwargs["_shell_executable"] = fact.shell_executable
|
|
269
266
|
|
|
270
267
|
command = _make_command(fact.command, fact_kwargs)
|
|
271
268
|
requires_command = _make_command(fact.requires_command, fact_kwargs)
|
|
@@ -284,6 +281,10 @@ def _get_fact(
|
|
|
284
281
|
status = False
|
|
285
282
|
output = CommandOutput([])
|
|
286
283
|
|
|
284
|
+
executor_kwargs = {
|
|
285
|
+
key: value for key, value in global_kwargs.items() if key in CONNECTOR_ARGUMENT_KEYS
|
|
286
|
+
}
|
|
287
|
+
|
|
287
288
|
try:
|
|
288
289
|
status, output = host.run_shell_command(
|
|
289
290
|
command,
|
|
@@ -295,7 +296,7 @@ def _get_fact(
|
|
|
295
296
|
log_host_command_error(
|
|
296
297
|
host,
|
|
297
298
|
e,
|
|
298
|
-
timeout=
|
|
299
|
+
timeout=global_kwargs["_timeout"],
|
|
299
300
|
)
|
|
300
301
|
|
|
301
302
|
stdout_lines, stderr_lines = output.stdout_lines, output.stderr_lines
|
|
@@ -334,12 +335,12 @@ def _get_fact(
|
|
|
334
335
|
|
|
335
336
|
log_error_or_warning(
|
|
336
337
|
host,
|
|
337
|
-
|
|
338
|
+
global_kwargs["_ignore_errors"],
|
|
338
339
|
description=("could not load fact: {0} {1}").format(name, get_kwargs_str(fact_kwargs)),
|
|
339
340
|
)
|
|
340
341
|
|
|
341
342
|
# Check we've not failed
|
|
342
|
-
if not status and not
|
|
343
|
+
if apply_failed_hosts and not status and not global_kwargs["_ignore_errors"]:
|
|
343
344
|
state.fail_hosts({host})
|
|
344
345
|
|
|
345
346
|
return data
|
|
@@ -33,7 +33,9 @@ if TYPE_CHECKING:
|
|
|
33
33
|
from pyinfra.api.state import State
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def extract_callable_datas(
|
|
36
|
+
def extract_callable_datas(
|
|
37
|
+
datas: list[Union[Callable[..., Any], Any]],
|
|
38
|
+
) -> Generator[Any, Any, Any]:
|
|
37
39
|
for data in datas:
|
|
38
40
|
# Support for dynamic data, ie @deploy wrapped data defaults where
|
|
39
41
|
# the data is stored on the state temporarily.
|
|
@@ -336,12 +338,10 @@ class Host:
|
|
|
336
338
|
T = TypeVar("T")
|
|
337
339
|
|
|
338
340
|
@overload
|
|
339
|
-
def get_fact(self, name_or_cls: Type[FactBase[T]], *args, **kwargs) -> T:
|
|
340
|
-
...
|
|
341
|
+
def get_fact(self, name_or_cls: Type[FactBase[T]], *args, **kwargs) -> T: ...
|
|
341
342
|
|
|
342
343
|
@overload
|
|
343
|
-
def get_fact(self, name_or_cls: Type[ShortFactBase[T]], *args, **kwargs) -> T:
|
|
344
|
-
...
|
|
344
|
+
def get_fact(self, name_or_cls: Type[ShortFactBase[T]], *args, **kwargs) -> T: ...
|
|
345
345
|
|
|
346
346
|
def get_fact(self, name_or_cls, *args, **kwargs):
|
|
347
347
|
"""
|