pyinfra 3.4__tar.gz → 3.5__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.4 → pyinfra-3.5}/CHANGELOG.md +35 -1
- {pyinfra-3.4/pyinfra.egg-info → pyinfra-3.5}/PKG-INFO +3 -3
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/arguments.py +63 -1
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/config.py +6 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/connect.py +19 -2
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/operation.py +54 -1
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/operations.py +119 -56
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/state.py +10 -2
- pyinfra-3.5/pyinfra/connectors/scp/__init__.py +1 -0
- pyinfra-3.5/pyinfra/connectors/scp/client.py +204 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/ssh.py +39 -7
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/util.py +4 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/dnf.py +8 -4
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/docker.py +28 -8
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/files.py +167 -26
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/server.py +55 -4
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/util/packaging.py +1 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/yum.py +8 -4
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/zypper.py +3 -3
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/crontab.py +1 -1
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/docker.py +130 -29
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/files.py +162 -7
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/git.py +1 -1
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/openrc.py +13 -7
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/pip.py +6 -7
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/pipx.py +19 -7
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/util/docker.py +49 -1
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/util/files.py +70 -2
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/util/packaging.py +98 -55
- {pyinfra-3.4 → pyinfra-3.5/pyinfra.egg-info}/PKG-INFO +3 -3
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra.egg-info/SOURCES.txt +2 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra.egg-info/requires.txt +2 -2
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/main.py +39 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/prints.py +4 -0
- {pyinfra-3.4 → pyinfra-3.5}/setup.py +1 -1
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_operations.py +348 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/test_cli.py +3 -0
- {pyinfra-3.4 → pyinfra-3.5}/LICENSE.md +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/MANIFEST.in +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/README.md +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/__main__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/arguments_typed.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/command.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/connectors.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/deploy.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/exceptions.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/facts.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/host.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/inventory.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/api/util.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/base.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/chroot.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/docker.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/dockerssh.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/local.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/ssh_util.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/sshuserclient/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/sshuserclient/client.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/sshuserclient/config.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/terraform.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/connectors/vagrant.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/context.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/apk.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/apt.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/brew.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/bsdinit.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/cargo.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/choco.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/crontab.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/deb.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/efibootmgr.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/flatpak.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/freebsd.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/gem.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/git.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/gpg.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/hardware.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/iptables.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/launchd.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/lxd.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/mysql.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/npm.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/openrc.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/opkg.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/pacman.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/pip.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/pipx.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/pkg.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/pkgin.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/podman.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/postgres.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/postgresql.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/rpm.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/runit.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/selinux.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/snap.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/systemd.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/sysvinit.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/upstart.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/util/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/util/databases.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/util/units.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/util/win_files.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/vzctl.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/xbps.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/facts/zfs.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/local.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/apk.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/apt.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/brew.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/bsdinit.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/cargo.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/choco.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/dnf.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/flatpak.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/freebsd/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/freebsd/freebsd_update.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/freebsd/pkg.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/freebsd/service.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/freebsd/sysrc.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/gem.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/iptables.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/launchd.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/lxd.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/mysql.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/npm.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/opkg.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/pacman.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/pkg.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/pkgin.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/postgres.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/postgresql.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/puppet.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/python.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/runit.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/selinux.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/server.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/snap.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/ssh.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/systemd.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/sysvinit.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/upstart.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/util/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/util/service.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/vzctl.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/xbps.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/yum.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/zfs.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/operations/zypper.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/progress.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/py.typed +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra/version.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra.egg-info/dependency_links.txt +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra.egg-info/entry_points.txt +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra.egg-info/top_level.txt +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/__main__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/commands.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/exceptions.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/inventory.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/log.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/util.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyinfra_cli/virtualenv.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/pyproject.toml +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/setup.cfg +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_arguments.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_command.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_config.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_deploys.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_facts.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_host.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_inventory.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_api/test_api_util.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/test_cli_deploy.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/test_cli_exceptions.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/test_cli_inventory.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/test_cli_util.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/test_context_objects.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_cli/util.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/__init__.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_chroot.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_docker.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_dockerssh.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_local.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_ssh.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_sshuserclient.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_terraform.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_util.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_connectors/test_vagrant.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_facts.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_global_arguments.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_operations.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_operations_utils.py +0 -0
- {pyinfra-3.4 → pyinfra-3.5}/tests/test_units.py +0 -0
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
# v3.5
|
|
2
|
+
|
|
3
|
+
New release with some really awesome new features, brought to you by the fantastic contributions of the community. New stuff:
|
|
4
|
+
|
|
5
|
+
- add `--diff` argument to show file diffs for potential file changes (@jgelens)
|
|
6
|
+
- add `_retries`, `_retry_delay` and `_retry_until` global arguments (@shohamd4)
|
|
7
|
+
- parallelize disconnecting from hosts (@gwelch-contegix)
|
|
8
|
+
- enable using SCP instead of SFTP for SSH file transfers (@DonDebonair)
|
|
9
|
+
|
|
10
|
+
New and updated operations/facts:
|
|
11
|
+
- facts/server: add `RebootRequired` fact (@wowi42)
|
|
12
|
+
- operations/pip: support PEP-508 package versions (@morrison12)
|
|
13
|
+
- operations+facts/docker: add Docker plugin support (@DonDebonair)
|
|
14
|
+
- operations/files.put: add `atime` and `mtime` arguments (@vram0gh2)
|
|
15
|
+
- operations/openrc: support runlevel when enabling services (@sengo4hd)
|
|
16
|
+
- facts/yum+dnf+zypper: return `repoid` in repository facts
|
|
17
|
+
|
|
18
|
+
Operation/fact fixes:
|
|
19
|
+
|
|
20
|
+
- facts/files.File: add ls fallback support (@mrkbac)
|
|
21
|
+
- operations/openrc: add missing noop messages (@sengo4hd)
|
|
22
|
+
- operations/server.crontab: fix newline when replacing existing values (@Nananas)
|
|
23
|
+
- operations/files.block: fix examples doc (@morrison12)
|
|
24
|
+
- operations/files.block: fix case where file exists but line is missing (@morrison12)
|
|
25
|
+
- operations/files.block: improve handling of special characters in marker lines (@morrison12)
|
|
26
|
+
|
|
27
|
+
Internal/meta:
|
|
28
|
+
|
|
29
|
+
- documentation link fix (@sengo4hd)
|
|
30
|
+
|
|
31
|
+
# v3.4.1
|
|
32
|
+
|
|
33
|
+
- fix config context when getting operation arguments
|
|
34
|
+
|
|
1
35
|
# v3.4
|
|
2
36
|
|
|
3
37
|
Much delayed 3.4, great collection of additions and improvements. Huge THANK YOU to all contributors as always. New features:
|
|
@@ -27,7 +61,7 @@ Operation/fact fixes:
|
|
|
27
61
|
|
|
28
62
|
Internal/meta:
|
|
29
63
|
|
|
30
|
-
- remove
|
|
64
|
+
- remove unnecessary setuptools runtime dependency (@karlicoss)
|
|
31
65
|
|
|
32
66
|
# v3.3.1
|
|
33
67
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyinfra
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5
|
|
4
4
|
Summary: pyinfra automates/provisions/manages/deploys infrastructure.
|
|
5
5
|
Home-page: https://pyinfra.com
|
|
6
6
|
Author: Nick / Fizzadar
|
|
@@ -52,7 +52,7 @@ Requires-Dist: flake8-isort==6.1.2; extra == "test"
|
|
|
52
52
|
Requires-Dist: pyyaml==6.0.2; extra == "test"
|
|
53
53
|
Requires-Dist: mypy; extra == "test"
|
|
54
54
|
Requires-Dist: types-cryptography; extra == "test"
|
|
55
|
-
Requires-Dist: types-paramiko; extra == "test"
|
|
55
|
+
Requires-Dist: types-paramiko<4; extra == "test"
|
|
56
56
|
Requires-Dist: types-python-dateutil; extra == "test"
|
|
57
57
|
Requires-Dist: types-PyYAML; extra == "test"
|
|
58
58
|
Provides-Extra: docs
|
|
@@ -73,7 +73,7 @@ Requires-Dist: flake8-isort==6.1.2; extra == "dev"
|
|
|
73
73
|
Requires-Dist: pyyaml==6.0.2; extra == "dev"
|
|
74
74
|
Requires-Dist: mypy; extra == "dev"
|
|
75
75
|
Requires-Dist: types-cryptography; extra == "dev"
|
|
76
|
-
Requires-Dist: types-paramiko; extra == "dev"
|
|
76
|
+
Requires-Dist: types-paramiko<4; extra == "dev"
|
|
77
77
|
Requires-Dist: types-python-dateutil; extra == "dev"
|
|
78
78
|
Requires-Dist: types-PyYAML; extra == "dev"
|
|
79
79
|
Requires-Dist: pyinfra-guzzle_sphinx_theme==0.17; extra == "dev"
|
|
@@ -72,6 +72,11 @@ class ConnectorArguments(TypedDict, total=False):
|
|
|
72
72
|
_get_pty: bool
|
|
73
73
|
_stdin: Union[str, Iterable[str]]
|
|
74
74
|
|
|
75
|
+
# Retry arguments
|
|
76
|
+
_retries: int
|
|
77
|
+
_retry_delay: Union[int, float]
|
|
78
|
+
_retry_until: Optional[Callable[[dict], bool]]
|
|
79
|
+
|
|
75
80
|
|
|
76
81
|
def generate_env(config: "Config", value: dict) -> dict:
|
|
77
82
|
env = config.ENV.copy()
|
|
@@ -232,11 +237,28 @@ def all_global_arguments() -> List[tuple[str, Type]]:
|
|
|
232
237
|
return list(get_type_hints(AllArguments).items())
|
|
233
238
|
|
|
234
239
|
|
|
240
|
+
# Create a dictionary for retry arguments
|
|
241
|
+
retry_argument_meta: dict[str, ArgumentMeta] = {
|
|
242
|
+
"_retries": ArgumentMeta(
|
|
243
|
+
"Number of times to retry failed operations.",
|
|
244
|
+
default=lambda config: config.RETRY,
|
|
245
|
+
),
|
|
246
|
+
"_retry_delay": ArgumentMeta(
|
|
247
|
+
"Delay in seconds between retry attempts.",
|
|
248
|
+
default=lambda config: config.RETRY_DELAY,
|
|
249
|
+
),
|
|
250
|
+
"_retry_until": ArgumentMeta(
|
|
251
|
+
"Callable taking output data that returns True to continue retrying.",
|
|
252
|
+
default=lambda config: None,
|
|
253
|
+
),
|
|
254
|
+
}
|
|
255
|
+
|
|
235
256
|
all_argument_meta: dict[str, ArgumentMeta] = {
|
|
236
257
|
**auth_argument_meta,
|
|
237
258
|
**shell_argument_meta,
|
|
238
259
|
**meta_argument_meta,
|
|
239
260
|
**execution_argument_meta,
|
|
261
|
+
**retry_argument_meta, # Add retry arguments
|
|
240
262
|
}
|
|
241
263
|
|
|
242
264
|
EXECUTION_KWARG_KEYS = list(ExecutionArguments.__annotations__.keys())
|
|
@@ -286,6 +308,45 @@ __argument_docs__ = {
|
|
|
286
308
|
),
|
|
287
309
|
"Operation meta & callbacks": (meta_argument_meta, "", ""),
|
|
288
310
|
"Execution strategy": (execution_argument_meta, "", ""),
|
|
311
|
+
"Retry behavior": (
|
|
312
|
+
retry_argument_meta,
|
|
313
|
+
"""
|
|
314
|
+
Retry arguments allow you to automatically retry operations that fail. You can specify
|
|
315
|
+
how many times to retry, the delay between retries, and optionally a condition
|
|
316
|
+
function to determine when to stop retrying.
|
|
317
|
+
""",
|
|
318
|
+
"""
|
|
319
|
+
.. code:: python
|
|
320
|
+
|
|
321
|
+
# Retry a command up to 3 times with the default 5 second delay
|
|
322
|
+
server.shell(
|
|
323
|
+
name="Run flaky command with retries",
|
|
324
|
+
commands=["flaky_command"],
|
|
325
|
+
_retries=3,
|
|
326
|
+
)
|
|
327
|
+
# Retry with a custom delay
|
|
328
|
+
server.shell(
|
|
329
|
+
name="Run flaky command with custom delay",
|
|
330
|
+
commands=["flaky_command"],
|
|
331
|
+
_retries=2,
|
|
332
|
+
_retry_delay=10, # 10 second delay between retries
|
|
333
|
+
)
|
|
334
|
+
# Retry with a custom condition
|
|
335
|
+
def retry_on_specific_error(output_data):
|
|
336
|
+
# Retry if stderr contains "temporary failure"
|
|
337
|
+
for line in output_data["stderr_lines"]:
|
|
338
|
+
if "temporary failure" in line.lower():
|
|
339
|
+
return True
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
server.shell(
|
|
343
|
+
name="Run command with conditional retry",
|
|
344
|
+
commands=["flaky_command"],
|
|
345
|
+
_retries=5,
|
|
346
|
+
_retry_until=retry_on_specific_error,
|
|
347
|
+
)
|
|
348
|
+
""",
|
|
349
|
+
),
|
|
289
350
|
}
|
|
290
351
|
|
|
291
352
|
|
|
@@ -305,7 +366,8 @@ def pop_global_arguments(
|
|
|
305
366
|
|
|
306
367
|
config = state.config
|
|
307
368
|
if ctx_config.isset():
|
|
308
|
-
config =
|
|
369
|
+
config = ctx_config.get()
|
|
370
|
+
assert config is not None
|
|
309
371
|
|
|
310
372
|
cdkwargs = host.current_deploy_kwargs
|
|
311
373
|
meta_kwargs: dict[str, Any] = cdkwargs or {} # type: ignore[assignment]
|
|
@@ -53,6 +53,12 @@ class ConfigDefaults:
|
|
|
53
53
|
IGNORE_ERRORS: bool = False
|
|
54
54
|
# Shell to use to execute commands
|
|
55
55
|
SHELL: str = "sh"
|
|
56
|
+
# Whether to display full diffs for files
|
|
57
|
+
DIFF: bool = False
|
|
58
|
+
# Number of times to retry failed operations
|
|
59
|
+
RETRY: int = 0
|
|
60
|
+
# Delay in seconds between retry attempts
|
|
61
|
+
RETRY_DELAY: int = 5
|
|
56
62
|
|
|
57
63
|
|
|
58
64
|
config_defaults = {key: value for key, value in ConfigDefaults.__dict__.items() if key.isupper()}
|
|
@@ -46,5 +46,22 @@ def connect_all(state: "State"):
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def disconnect_all(state: "State"):
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
"""
|
|
50
|
+
Disconnect from all of the configured servers in parallel. Reads/writes state.inventory.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
state (``pyinfra.api.State`` obj): the state containing an inventory to connect to
|
|
54
|
+
"""
|
|
55
|
+
greenlet_to_host = {
|
|
56
|
+
state.pool.spawn(host.disconnect): host
|
|
57
|
+
for host in state.activated_hosts # only hosts we connected to please!
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
with progress_spinner(greenlet_to_host.values()) as progress:
|
|
61
|
+
for greenlet in gevent.iwait(greenlet_to_host.keys()):
|
|
62
|
+
host = greenlet_to_host[greenlet]
|
|
63
|
+
progress(host)
|
|
64
|
+
|
|
65
|
+
for greenlet, host in greenlet_to_host.items():
|
|
66
|
+
# Raise any unexpected exception
|
|
67
|
+
greenlet.get()
|
|
@@ -47,6 +47,9 @@ class OperationMeta:
|
|
|
47
47
|
_commands: Optional[list[Any]] = None
|
|
48
48
|
_maybe_is_change: Optional[bool] = None
|
|
49
49
|
_success: Optional[bool] = None
|
|
50
|
+
_retry_attempts: int = 0
|
|
51
|
+
_max_retries: int = 0
|
|
52
|
+
_retry_succeeded: Optional[bool] = None
|
|
50
53
|
|
|
51
54
|
def __init__(self, hash, is_change: Optional[bool]):
|
|
52
55
|
self._hash = hash
|
|
@@ -59,9 +62,17 @@ class OperationMeta:
|
|
|
59
62
|
"""
|
|
60
63
|
|
|
61
64
|
if self._commands is not None:
|
|
65
|
+
retry_info = ""
|
|
66
|
+
if self._retry_attempts > 0:
|
|
67
|
+
retry_result = "succeeded" if self._retry_succeeded else "failed"
|
|
68
|
+
retry_info = (
|
|
69
|
+
f", retries={self._retry_attempts}/{self._max_retries} ({retry_result})"
|
|
70
|
+
)
|
|
71
|
+
|
|
62
72
|
return (
|
|
63
73
|
"OperationMeta(executed=True, "
|
|
64
|
-
f"success={self.did_succeed()}, hash={self._hash},
|
|
74
|
+
f"success={self.did_succeed()}, hash={self._hash}, "
|
|
75
|
+
f"commands={len(self._commands)}{retry_info})"
|
|
65
76
|
)
|
|
66
77
|
return (
|
|
67
78
|
"OperationMeta(executed=False, "
|
|
@@ -74,12 +85,20 @@ class OperationMeta:
|
|
|
74
85
|
success: bool,
|
|
75
86
|
commands: list[Any],
|
|
76
87
|
combined_output: "CommandOutput",
|
|
88
|
+
retry_attempts: int = 0,
|
|
89
|
+
max_retries: int = 0,
|
|
77
90
|
) -> None:
|
|
78
91
|
if self.is_complete():
|
|
79
92
|
raise RuntimeError("Cannot complete an already complete operation")
|
|
80
93
|
self._success = success
|
|
81
94
|
self._commands = commands
|
|
82
95
|
self._combined_output = combined_output
|
|
96
|
+
self._retry_attempts = retry_attempts
|
|
97
|
+
self._max_retries = max_retries
|
|
98
|
+
|
|
99
|
+
# Determine if operation succeeded after retries
|
|
100
|
+
if retry_attempts > 0:
|
|
101
|
+
self._retry_succeeded = success
|
|
83
102
|
|
|
84
103
|
def is_complete(self) -> bool:
|
|
85
104
|
return self._success is not None
|
|
@@ -150,6 +169,40 @@ class OperationMeta:
|
|
|
150
169
|
def stderr(self) -> str:
|
|
151
170
|
return "\n".join(self.stderr_lines)
|
|
152
171
|
|
|
172
|
+
@property
|
|
173
|
+
def retry_attempts(self) -> int:
|
|
174
|
+
return self._retry_attempts
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def max_retries(self) -> int:
|
|
178
|
+
return self._max_retries
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def was_retried(self) -> bool:
|
|
182
|
+
"""
|
|
183
|
+
Returns whether this operation was retried at least once.
|
|
184
|
+
"""
|
|
185
|
+
return self._retry_attempts > 0
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def retry_succeeded(self) -> Optional[bool]:
|
|
189
|
+
"""
|
|
190
|
+
Returns whether this operation succeeded after retries.
|
|
191
|
+
Returns None if the operation was not retried.
|
|
192
|
+
"""
|
|
193
|
+
return self._retry_succeeded
|
|
194
|
+
|
|
195
|
+
def get_retry_info(self) -> dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Returns a dictionary with all retry-related information.
|
|
198
|
+
"""
|
|
199
|
+
return {
|
|
200
|
+
"retry_attempts": self._retry_attempts,
|
|
201
|
+
"max_retries": self._max_retries,
|
|
202
|
+
"was_retried": self.was_retried,
|
|
203
|
+
"retry_succeeded": self._retry_succeeded,
|
|
204
|
+
}
|
|
205
|
+
|
|
153
206
|
|
|
154
207
|
def add_op(state: State, op_func, *args, **kwargs):
|
|
155
208
|
"""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import time
|
|
3
4
|
import traceback
|
|
4
5
|
from itertools import product
|
|
5
6
|
from socket import error as socket_error, timeout as timeout_error
|
|
@@ -66,6 +67,11 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
|
66
67
|
continue_on_error = global_arguments["_continue_on_error"]
|
|
67
68
|
timeout = global_arguments.get("_timeout", 0)
|
|
68
69
|
|
|
70
|
+
# Extract retry arguments
|
|
71
|
+
retries = global_arguments.get("_retries", 0)
|
|
72
|
+
retry_delay = global_arguments.get("_retry_delay", 5)
|
|
73
|
+
retry_until = global_arguments.get("_retry_until", None)
|
|
74
|
+
|
|
69
75
|
executor_kwarg_keys = CONNECTOR_ARGUMENT_KEYS
|
|
70
76
|
# See: https://github.com/python/mypy/issues/10371
|
|
71
77
|
base_connector_arguments: ConnectorArguments = cast(
|
|
@@ -73,67 +79,114 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
|
73
79
|
{key: global_arguments[key] for key in executor_kwarg_keys if key in global_arguments}, # type: ignore[literal-required] # noqa
|
|
74
80
|
)
|
|
75
81
|
|
|
82
|
+
retry_attempt = 0
|
|
76
83
|
did_error = False
|
|
77
84
|
executed_commands = 0
|
|
78
|
-
commands = []
|
|
85
|
+
commands: list[PyinfraCommand] = []
|
|
79
86
|
all_output_lines: list[OutputLine] = []
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
status
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
88
|
+
# Retry loop
|
|
89
|
+
while retry_attempt <= retries:
|
|
90
|
+
did_error = False
|
|
91
|
+
executed_commands = 0
|
|
92
|
+
commands = []
|
|
93
|
+
all_output_lines = []
|
|
94
|
+
|
|
95
|
+
for command in op_data.command_generator():
|
|
96
|
+
commands.append(command)
|
|
97
|
+
status = False
|
|
98
|
+
connector_arguments = base_connector_arguments.copy()
|
|
99
|
+
connector_arguments.update(command.connector_arguments)
|
|
100
|
+
|
|
101
|
+
if not isinstance(command, PyinfraCommand):
|
|
102
|
+
raise TypeError("{0} is an invalid pyinfra command!".format(command))
|
|
103
|
+
|
|
104
|
+
if isinstance(command, FunctionCommand):
|
|
105
|
+
try:
|
|
106
|
+
status = command.execute(state, host, connector_arguments)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
# Custom functions could do anything, so expect anything!
|
|
109
|
+
logger.warning(traceback.format_exc())
|
|
110
|
+
host.log_styled(
|
|
111
|
+
f"Unexpected error in Python callback: {format_exception(e)}",
|
|
112
|
+
fg="red",
|
|
113
|
+
log_func=logger.warning,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
elif isinstance(command, StringCommand):
|
|
117
|
+
output_lines = CommandOutput([])
|
|
118
|
+
try:
|
|
119
|
+
status, output_lines = command.execute(
|
|
120
|
+
state,
|
|
121
|
+
host,
|
|
122
|
+
connector_arguments,
|
|
123
|
+
)
|
|
124
|
+
except (timeout_error, socket_error, SSHException) as e:
|
|
125
|
+
log_host_command_error(host, e, timeout=timeout)
|
|
126
|
+
all_output_lines.extend(output_lines)
|
|
127
|
+
# If we failed and have not already printed the stderr, print it
|
|
128
|
+
if status is False and not state.print_output:
|
|
129
|
+
print_host_combined_output(host, output_lines)
|
|
130
|
+
|
|
131
|
+
else:
|
|
132
|
+
try:
|
|
133
|
+
status = command.execute(state, host, connector_arguments)
|
|
134
|
+
except (timeout_error, socket_error, SSHException, IOError) as e:
|
|
135
|
+
log_host_command_error(host, e, timeout=timeout)
|
|
136
|
+
|
|
137
|
+
# Break the loop to trigger a failure
|
|
138
|
+
if status is False:
|
|
139
|
+
did_error = True
|
|
140
|
+
if continue_on_error is True:
|
|
141
|
+
continue
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
executed_commands += 1
|
|
145
|
+
|
|
146
|
+
# Check if we should retry
|
|
147
|
+
should_retry = False
|
|
148
|
+
if retry_attempt < retries:
|
|
149
|
+
# Retry on error
|
|
150
|
+
if did_error:
|
|
151
|
+
should_retry = True
|
|
152
|
+
# Retry on condition if no error
|
|
153
|
+
elif retry_until and not did_error:
|
|
154
|
+
try:
|
|
155
|
+
output_data = {
|
|
156
|
+
"stdout_lines": [
|
|
157
|
+
line.line for line in all_output_lines if line.buffer_name == "stdout"
|
|
158
|
+
],
|
|
159
|
+
"stderr_lines": [
|
|
160
|
+
line.line for line in all_output_lines if line.buffer_name == "stderr"
|
|
161
|
+
],
|
|
162
|
+
"commands": [str(command) for command in commands],
|
|
163
|
+
"executed_commands": executed_commands,
|
|
164
|
+
"host": host.name,
|
|
165
|
+
"operation": ", ".join(state.get_op_meta(op_hash).names) or "Operation",
|
|
166
|
+
}
|
|
167
|
+
should_retry = retry_until(output_data)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
host.log_styled(
|
|
170
|
+
f"Error in retry_until function: {format_exception(e)}",
|
|
171
|
+
fg="red",
|
|
172
|
+
log_func=logger.warning,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if should_retry:
|
|
176
|
+
retry_attempt += 1
|
|
177
|
+
state.trigger_callbacks("operation_host_retry", host, op_hash, retry_attempt, retries)
|
|
178
|
+
op_name = ", ".join(state.get_op_meta(op_hash).names) or "Operation"
|
|
179
|
+
host.log_styled(
|
|
180
|
+
f"Retrying {op_name} (attempt {retry_attempt}/{retries}) after {retry_delay}s...",
|
|
181
|
+
fg="yellow",
|
|
182
|
+
log_func=logger.info,
|
|
183
|
+
)
|
|
184
|
+
time.sleep(retry_delay)
|
|
185
|
+
continue
|
|
131
186
|
|
|
132
|
-
|
|
187
|
+
break
|
|
133
188
|
|
|
134
189
|
# Handle results
|
|
135
|
-
#
|
|
136
|
-
|
|
137
190
|
op_success = return_status = not did_error
|
|
138
191
|
host_results = state.get_results_for_host(host)
|
|
139
192
|
|
|
@@ -142,10 +195,13 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
|
142
195
|
host_results.success_ops += 1
|
|
143
196
|
|
|
144
197
|
_status_log = "Success" if executed_commands > 0 else "No changes"
|
|
198
|
+
if retry_attempt > 0:
|
|
199
|
+
_status_log = f"{_status_log} on retry {retry_attempt}"
|
|
200
|
+
|
|
145
201
|
_click_log_status = click.style(_status_log, "green")
|
|
146
202
|
logger.info("{0}{1}".format(host.print_prefix, _click_log_status))
|
|
147
203
|
|
|
148
|
-
state.trigger_callbacks("operation_host_success", host, op_hash)
|
|
204
|
+
state.trigger_callbacks("operation_host_success", host, op_hash, retry_attempt)
|
|
149
205
|
else:
|
|
150
206
|
if ignore_errors:
|
|
151
207
|
host_results.ignored_error_ops += 1
|
|
@@ -156,6 +212,11 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
|
156
212
|
host_results.partial_ops += 1
|
|
157
213
|
|
|
158
214
|
_command_description = f"executed {executed_commands} commands"
|
|
215
|
+
if retry_attempt > 0:
|
|
216
|
+
_command_description = (
|
|
217
|
+
f"{_command_description} (failed after {retry_attempt}/{retries} retries)"
|
|
218
|
+
)
|
|
219
|
+
|
|
159
220
|
log_error_or_warning(host, ignore_errors, _command_description, continue_on_error)
|
|
160
221
|
|
|
161
222
|
# Ignored, op "completes" w/ ignored error
|
|
@@ -164,12 +225,14 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
|
164
225
|
return_status = True
|
|
165
226
|
|
|
166
227
|
# Unignored error -> False
|
|
167
|
-
state.trigger_callbacks("operation_host_error", host, op_hash)
|
|
228
|
+
state.trigger_callbacks("operation_host_error", host, op_hash, retry_attempt, retries)
|
|
168
229
|
|
|
169
230
|
op_data.operation_meta.set_complete(
|
|
170
231
|
op_success,
|
|
171
232
|
commands,
|
|
172
233
|
CommandOutput(all_output_lines),
|
|
234
|
+
retry_attempts=retry_attempt,
|
|
235
|
+
max_retries=retries,
|
|
173
236
|
)
|
|
174
237
|
|
|
175
238
|
return return_status
|
|
@@ -70,11 +70,19 @@ class BaseStateCallback:
|
|
|
70
70
|
pass
|
|
71
71
|
|
|
72
72
|
@staticmethod
|
|
73
|
-
def operation_host_success(state: "State", host: "Host", op_hash):
|
|
73
|
+
def operation_host_success(state: "State", host: "Host", op_hash, retry_count: int = 0):
|
|
74
74
|
pass
|
|
75
75
|
|
|
76
76
|
@staticmethod
|
|
77
|
-
def operation_host_error(
|
|
77
|
+
def operation_host_error(
|
|
78
|
+
state: "State", host: "Host", op_hash, retry_count: int = 0, max_retries: int = 0
|
|
79
|
+
):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def operation_host_retry(
|
|
84
|
+
state: "State", host: "Host", op_hash, retry_num: int, max_retries: int
|
|
85
|
+
):
|
|
78
86
|
pass
|
|
79
87
|
|
|
80
88
|
@staticmethod
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .client import SCPClient # noqa: F401
|