pyinfra 0.11.dev3__py3-none-any.whl → 3.6__py3-none-any.whl
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/__init__.py +9 -12
- pyinfra/__main__.py +4 -0
- pyinfra/api/__init__.py +19 -3
- pyinfra/api/arguments.py +413 -0
- pyinfra/api/arguments_typed.py +79 -0
- pyinfra/api/command.py +274 -0
- pyinfra/api/config.py +222 -28
- pyinfra/api/connect.py +33 -13
- pyinfra/api/connectors.py +27 -0
- pyinfra/api/deploy.py +65 -66
- pyinfra/api/exceptions.py +73 -18
- pyinfra/api/facts.py +267 -200
- pyinfra/api/host.py +416 -50
- pyinfra/api/inventory.py +121 -160
- pyinfra/api/metadata.py +69 -0
- pyinfra/api/operation.py +432 -262
- pyinfra/api/operations.py +273 -260
- pyinfra/api/state.py +302 -248
- pyinfra/api/util.py +309 -369
- pyinfra/connectors/base.py +173 -0
- pyinfra/connectors/chroot.py +212 -0
- pyinfra/connectors/docker.py +405 -0
- pyinfra/connectors/dockerssh.py +297 -0
- pyinfra/connectors/local.py +238 -0
- pyinfra/connectors/scp/__init__.py +1 -0
- pyinfra/connectors/scp/client.py +204 -0
- pyinfra/connectors/ssh.py +727 -0
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +309 -0
- pyinfra/connectors/sshuserclient/config.py +102 -0
- pyinfra/connectors/terraform.py +135 -0
- pyinfra/connectors/util.py +417 -0
- pyinfra/connectors/vagrant.py +183 -0
- pyinfra/context.py +145 -0
- pyinfra/facts/__init__.py +7 -6
- pyinfra/facts/apk.py +22 -7
- pyinfra/facts/apt.py +117 -60
- pyinfra/facts/brew.py +100 -15
- pyinfra/facts/bsdinit.py +23 -0
- pyinfra/facts/cargo.py +37 -0
- pyinfra/facts/choco.py +47 -0
- pyinfra/facts/crontab.py +195 -0
- pyinfra/facts/deb.py +94 -0
- pyinfra/facts/dnf.py +48 -0
- pyinfra/facts/docker.py +96 -23
- pyinfra/facts/efibootmgr.py +113 -0
- pyinfra/facts/files.py +629 -58
- pyinfra/facts/flatpak.py +77 -0
- pyinfra/facts/freebsd.py +70 -0
- pyinfra/facts/gem.py +19 -6
- pyinfra/facts/git.py +59 -14
- pyinfra/facts/gpg.py +150 -0
- pyinfra/facts/hardware.py +313 -167
- pyinfra/facts/iptables.py +72 -62
- pyinfra/facts/launchd.py +44 -0
- pyinfra/facts/lxd.py +17 -4
- pyinfra/facts/mysql.py +122 -86
- pyinfra/facts/npm.py +17 -9
- pyinfra/facts/openrc.py +71 -0
- pyinfra/facts/opkg.py +246 -0
- pyinfra/facts/pacman.py +50 -7
- pyinfra/facts/pip.py +24 -7
- pyinfra/facts/pipx.py +82 -0
- pyinfra/facts/pkg.py +15 -6
- pyinfra/facts/pkgin.py +35 -0
- pyinfra/facts/podman.py +54 -0
- pyinfra/facts/postgres.py +178 -0
- pyinfra/facts/postgresql.py +6 -147
- pyinfra/facts/rpm.py +105 -0
- pyinfra/facts/runit.py +77 -0
- pyinfra/facts/selinux.py +161 -0
- pyinfra/facts/server.py +762 -285
- pyinfra/facts/snap.py +88 -0
- pyinfra/facts/systemd.py +139 -0
- pyinfra/facts/sysvinit.py +59 -0
- pyinfra/facts/upstart.py +35 -0
- pyinfra/facts/util/__init__.py +17 -0
- pyinfra/facts/util/databases.py +4 -6
- pyinfra/facts/util/packaging.py +37 -6
- pyinfra/facts/util/units.py +30 -0
- pyinfra/facts/util/win_files.py +99 -0
- pyinfra/facts/vzctl.py +20 -13
- pyinfra/facts/xbps.py +35 -0
- pyinfra/facts/yum.py +34 -40
- pyinfra/facts/zfs.py +77 -0
- pyinfra/facts/zypper.py +42 -0
- pyinfra/local.py +45 -83
- pyinfra/operations/__init__.py +12 -0
- pyinfra/operations/apk.py +99 -0
- pyinfra/operations/apt.py +496 -0
- pyinfra/operations/brew.py +232 -0
- pyinfra/operations/bsdinit.py +59 -0
- pyinfra/operations/cargo.py +45 -0
- pyinfra/operations/choco.py +61 -0
- pyinfra/operations/crontab.py +194 -0
- pyinfra/operations/dnf.py +213 -0
- pyinfra/operations/docker.py +492 -0
- pyinfra/operations/files.py +2014 -0
- pyinfra/operations/flatpak.py +95 -0
- pyinfra/operations/freebsd/__init__.py +12 -0
- pyinfra/operations/freebsd/freebsd_update.py +70 -0
- pyinfra/operations/freebsd/pkg.py +219 -0
- pyinfra/operations/freebsd/service.py +116 -0
- pyinfra/operations/freebsd/sysrc.py +92 -0
- pyinfra/operations/gem.py +48 -0
- pyinfra/operations/git.py +420 -0
- pyinfra/operations/iptables.py +312 -0
- pyinfra/operations/launchd.py +45 -0
- pyinfra/operations/lxd.py +69 -0
- pyinfra/operations/mysql.py +610 -0
- pyinfra/operations/npm.py +57 -0
- pyinfra/operations/openrc.py +63 -0
- pyinfra/operations/opkg.py +89 -0
- pyinfra/operations/pacman.py +82 -0
- pyinfra/operations/pip.py +206 -0
- pyinfra/operations/pipx.py +103 -0
- pyinfra/operations/pkg.py +71 -0
- pyinfra/operations/pkgin.py +92 -0
- pyinfra/operations/postgres.py +437 -0
- pyinfra/operations/postgresql.py +30 -0
- pyinfra/operations/puppet.py +41 -0
- pyinfra/operations/python.py +73 -0
- pyinfra/operations/runit.py +184 -0
- pyinfra/operations/selinux.py +190 -0
- pyinfra/operations/server.py +1100 -0
- pyinfra/operations/snap.py +118 -0
- pyinfra/operations/ssh.py +217 -0
- pyinfra/operations/systemd.py +150 -0
- pyinfra/operations/sysvinit.py +142 -0
- pyinfra/operations/upstart.py +68 -0
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +407 -0
- pyinfra/operations/util/files.py +247 -0
- pyinfra/operations/util/packaging.py +338 -0
- pyinfra/operations/util/service.py +46 -0
- pyinfra/operations/vzctl.py +137 -0
- pyinfra/operations/xbps.py +78 -0
- pyinfra/operations/yum.py +213 -0
- pyinfra/operations/zfs.py +176 -0
- pyinfra/operations/zypper.py +193 -0
- pyinfra/progress.py +44 -32
- pyinfra/py.typed +0 -0
- pyinfra/version.py +9 -1
- pyinfra-3.6.dist-info/METADATA +142 -0
- pyinfra-3.6.dist-info/RECORD +160 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
- pyinfra-3.6.dist-info/entry_points.txt +12 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
- pyinfra_cli/__init__.py +1 -0
- pyinfra_cli/cli.py +793 -0
- pyinfra_cli/commands.py +66 -0
- pyinfra_cli/exceptions.py +155 -65
- pyinfra_cli/inventory.py +233 -89
- pyinfra_cli/log.py +39 -43
- pyinfra_cli/main.py +26 -495
- pyinfra_cli/prints.py +215 -156
- pyinfra_cli/util.py +172 -105
- pyinfra_cli/virtualenv.py +25 -20
- pyinfra/api/connectors/__init__.py +0 -21
- pyinfra/api/connectors/ansible.py +0 -99
- pyinfra/api/connectors/docker.py +0 -178
- pyinfra/api/connectors/local.py +0 -169
- pyinfra/api/connectors/ssh.py +0 -402
- pyinfra/api/connectors/sshuserclient/client.py +0 -105
- pyinfra/api/connectors/sshuserclient/config.py +0 -90
- pyinfra/api/connectors/util.py +0 -63
- pyinfra/api/connectors/vagrant.py +0 -155
- pyinfra/facts/init.py +0 -176
- pyinfra/facts/util/files.py +0 -102
- pyinfra/hook.py +0 -41
- pyinfra/modules/__init__.py +0 -11
- pyinfra/modules/apk.py +0 -64
- pyinfra/modules/apt.py +0 -272
- pyinfra/modules/brew.py +0 -122
- pyinfra/modules/files.py +0 -711
- pyinfra/modules/gem.py +0 -30
- pyinfra/modules/git.py +0 -115
- pyinfra/modules/init.py +0 -344
- pyinfra/modules/iptables.py +0 -271
- pyinfra/modules/lxd.py +0 -45
- pyinfra/modules/mysql.py +0 -347
- pyinfra/modules/npm.py +0 -47
- pyinfra/modules/pacman.py +0 -60
- pyinfra/modules/pip.py +0 -99
- pyinfra/modules/pkg.py +0 -43
- pyinfra/modules/postgresql.py +0 -245
- pyinfra/modules/puppet.py +0 -20
- pyinfra/modules/python.py +0 -37
- pyinfra/modules/server.py +0 -524
- pyinfra/modules/ssh.py +0 -150
- pyinfra/modules/util/files.py +0 -52
- pyinfra/modules/util/packaging.py +0 -118
- pyinfra/modules/vzctl.py +0 -133
- pyinfra/modules/yum.py +0 -171
- pyinfra/pseudo_modules.py +0 -64
- pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
- pyinfra-0.11.dev3.dist-info/METADATA +0 -135
- pyinfra-0.11.dev3.dist-info/RECORD +0 -95
- pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
- pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
- pyinfra_cli/__main__.py +0 -40
- pyinfra_cli/config.py +0 -92
- /pyinfra/{modules/util → connectors}/__init__.py +0 -0
- /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
pyinfra_cli/util.py
CHANGED
|
@@ -1,98 +1,143 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
3
5
|
from datetime import datetime
|
|
4
6
|
from importlib import import_module
|
|
7
|
+
from importlib.util import find_spec
|
|
5
8
|
from io import IOBase
|
|
6
|
-
from
|
|
9
|
+
from os import path
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import CodeType, FunctionType, ModuleType
|
|
12
|
+
from typing import Callable
|
|
7
13
|
|
|
8
14
|
import click
|
|
15
|
+
import gevent
|
|
16
|
+
|
|
17
|
+
from pyinfra import logger, state
|
|
18
|
+
from pyinfra.api.command import PyinfraCommand
|
|
19
|
+
from pyinfra.api.exceptions import PyinfraError
|
|
20
|
+
from pyinfra.api.host import HostData
|
|
21
|
+
from pyinfra.api.operation import OperationMeta
|
|
22
|
+
from pyinfra.api.state import (
|
|
23
|
+
State,
|
|
24
|
+
StateHostMeta,
|
|
25
|
+
StateHostResults,
|
|
26
|
+
StateOperationHostData,
|
|
27
|
+
StateOperationMeta,
|
|
28
|
+
)
|
|
29
|
+
from pyinfra.context import ctx_config, ctx_host
|
|
30
|
+
from pyinfra.progress import progress_spinner
|
|
31
|
+
|
|
32
|
+
from .exceptions import CliError, UnexpectedExternalError
|
|
9
33
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from pyinfra.hook import HOOKS
|
|
34
|
+
# Cache for compiled Python deploy code
|
|
35
|
+
PYTHON_CODES: dict[str, CodeType] = {}
|
|
13
36
|
|
|
14
|
-
from .exceptions import CliError
|
|
15
37
|
|
|
16
|
-
|
|
17
|
-
|
|
38
|
+
def is_subdir(child, parent):
|
|
39
|
+
child = path.realpath(child)
|
|
40
|
+
parent = path.realpath(parent)
|
|
41
|
+
relative = path.relpath(child, start=parent)
|
|
42
|
+
return not relative.startswith(os.pardir)
|
|
18
43
|
|
|
19
44
|
|
|
20
|
-
def exec_file(filename, return_locals=False, is_deploy_code=False):
|
|
21
|
-
|
|
45
|
+
def exec_file(filename, return_locals: bool = False, is_deploy_code: bool = False):
|
|
46
|
+
"""
|
|
22
47
|
Execute a Python file and optionally return it's attributes as a dict.
|
|
23
|
-
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
old_current_exec_filename = state.current_exec_filename
|
|
51
|
+
state.current_exec_filename = filename
|
|
24
52
|
|
|
25
53
|
if filename not in PYTHON_CODES:
|
|
26
|
-
with open(filename,
|
|
27
|
-
|
|
54
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
55
|
+
code_str = f.read()
|
|
28
56
|
|
|
29
|
-
code = compile(
|
|
57
|
+
code = compile(code_str, filename, "exec")
|
|
30
58
|
PYTHON_CODES[filename] = code
|
|
31
59
|
|
|
32
60
|
# Create some base attributes for our "module"
|
|
33
|
-
data = {
|
|
34
|
-
'__file__': filename,
|
|
35
|
-
'state': pseudo_state,
|
|
36
|
-
}
|
|
61
|
+
data = {"__file__": filename}
|
|
37
62
|
|
|
38
63
|
# Execute the code with locals/globals going into the dict above
|
|
39
|
-
|
|
64
|
+
try:
|
|
65
|
+
exec(PYTHON_CODES[filename], data)
|
|
66
|
+
except PyinfraError:
|
|
67
|
+
# Raise pyinfra errors as-is
|
|
68
|
+
raise
|
|
69
|
+
except Exception as e:
|
|
70
|
+
# Wrap & re-raise errors in user code so we highlight filename/etc
|
|
71
|
+
raise UnexpectedExternalError(e, filename)
|
|
72
|
+
finally:
|
|
73
|
+
state.current_exec_filename = old_current_exec_filename
|
|
40
74
|
|
|
41
75
|
return data
|
|
42
76
|
|
|
43
77
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for hook in hooks:
|
|
49
|
-
print('--> Running hook: {0}/{1}'.format(
|
|
50
|
-
hook_name,
|
|
51
|
-
click.style(hook.__name__, bold=True),
|
|
52
|
-
))
|
|
53
|
-
hook(hook_data, state)
|
|
54
|
-
|
|
55
|
-
print()
|
|
78
|
+
def json_encode(obj):
|
|
79
|
+
# pyinfra types
|
|
80
|
+
if isinstance(obj, HostData):
|
|
81
|
+
return obj.dict()
|
|
56
82
|
|
|
83
|
+
if isinstance(obj, PyinfraCommand):
|
|
84
|
+
return repr(obj)
|
|
85
|
+
|
|
86
|
+
if isinstance(
|
|
87
|
+
obj,
|
|
88
|
+
(
|
|
89
|
+
OperationMeta,
|
|
90
|
+
StateOperationMeta,
|
|
91
|
+
StateOperationHostData,
|
|
92
|
+
StateHostMeta,
|
|
93
|
+
StateHostResults,
|
|
94
|
+
),
|
|
95
|
+
):
|
|
96
|
+
return repr(obj)
|
|
97
|
+
|
|
98
|
+
# Python types
|
|
99
|
+
if isinstance(obj, ModuleType):
|
|
100
|
+
return "Module: {0}".format(obj.__name__)
|
|
57
101
|
|
|
58
|
-
def json_encode(obj):
|
|
59
102
|
if isinstance(obj, FunctionType):
|
|
60
|
-
return obj.__name__
|
|
103
|
+
return "Function: {0}".format(obj.__name__)
|
|
61
104
|
|
|
62
|
-
|
|
105
|
+
if isinstance(obj, datetime):
|
|
63
106
|
return obj.isoformat()
|
|
64
107
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
elif isinstance(obj, IOBase):
|
|
69
|
-
if hasattr(obj, 'name'):
|
|
70
|
-
return 'File: {0}'.format(obj.name)
|
|
108
|
+
if isinstance(obj, IOBase):
|
|
109
|
+
if hasattr(obj, "name"):
|
|
110
|
+
return "File: {0}".format(obj.name)
|
|
71
111
|
|
|
72
|
-
|
|
73
|
-
return
|
|
112
|
+
if hasattr(obj, "template"):
|
|
113
|
+
return "Template: {0}".format(obj.template)
|
|
74
114
|
|
|
75
115
|
obj.seek(0)
|
|
76
|
-
return
|
|
116
|
+
return "In memory file: {0}".format(obj.read())
|
|
117
|
+
|
|
118
|
+
if isinstance(obj, Path):
|
|
119
|
+
return str(obj)
|
|
77
120
|
|
|
78
|
-
|
|
121
|
+
if isinstance(obj, set):
|
|
79
122
|
return sorted(list(obj))
|
|
80
123
|
|
|
81
|
-
|
|
124
|
+
if isinstance(obj, bytes):
|
|
82
125
|
return obj.decode()
|
|
83
126
|
|
|
84
|
-
|
|
85
|
-
|
|
127
|
+
if hasattr(obj, "to_json"):
|
|
128
|
+
return obj.to_json()
|
|
129
|
+
|
|
130
|
+
raise TypeError("Cannot serialize: {0} ({1})".format(type(obj), obj))
|
|
86
131
|
|
|
87
132
|
|
|
88
|
-
def
|
|
133
|
+
def parse_cli_arg(arg):
|
|
89
134
|
if isinstance(arg, list):
|
|
90
|
-
return [
|
|
135
|
+
return [parse_cli_arg(a) for a in arg]
|
|
91
136
|
|
|
92
|
-
if arg.lower() ==
|
|
137
|
+
if arg.lower() == "false":
|
|
93
138
|
return False
|
|
94
139
|
|
|
95
|
-
if arg.lower() ==
|
|
140
|
+
if arg.lower() == "true":
|
|
96
141
|
return True
|
|
97
142
|
|
|
98
143
|
try:
|
|
@@ -100,73 +145,95 @@ def _parse_arg(arg):
|
|
|
100
145
|
except (TypeError, ValueError):
|
|
101
146
|
pass
|
|
102
147
|
|
|
148
|
+
try:
|
|
149
|
+
return json.loads(arg)
|
|
150
|
+
except ValueError:
|
|
151
|
+
pass
|
|
152
|
+
|
|
103
153
|
return arg
|
|
104
154
|
|
|
105
155
|
|
|
106
|
-
def
|
|
107
|
-
|
|
156
|
+
def try_import_module_attribute(path, prefix=None, raise_for_none=True):
|
|
157
|
+
if ":" in path:
|
|
158
|
+
# Allow a.module.name:function syntax
|
|
159
|
+
mod_path, attr_name = path.rsplit(":", 1)
|
|
160
|
+
elif "." in path:
|
|
161
|
+
# And also a.module.name.function
|
|
162
|
+
mod_path, attr_name = path.rsplit(".", 1)
|
|
163
|
+
else:
|
|
164
|
+
return None
|
|
108
165
|
|
|
109
|
-
|
|
110
|
-
|
|
166
|
+
possible_modules = [mod_path]
|
|
167
|
+
if prefix:
|
|
168
|
+
possible_modules.append(f"{prefix}.{mod_path}")
|
|
111
169
|
|
|
112
|
-
|
|
113
|
-
op_module = import_module('pyinfra.modules.{0}'.format(op_module))
|
|
114
|
-
except ImportError:
|
|
115
|
-
raise CliError('No such module: {0}'.format(op_module))
|
|
170
|
+
module = None
|
|
116
171
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
172
|
+
for possible in possible_modules:
|
|
173
|
+
try:
|
|
174
|
+
# Look for the module/fn, note that from the find_spec doc:
|
|
175
|
+
# "If the name is for submodule (contains a dot), the parent module is
|
|
176
|
+
# automatically imported."
|
|
177
|
+
spec = find_spec(possible)
|
|
178
|
+
except ModuleNotFoundError:
|
|
179
|
+
continue
|
|
180
|
+
except Exception as e:
|
|
181
|
+
# Capture all exceptions here which may be triggered from the automatic module import
|
|
182
|
+
# referenced above the find_spec call.
|
|
183
|
+
logger.warning(f"Exception raised during inventory search on: {possible}: {e}")
|
|
184
|
+
continue
|
|
185
|
+
else:
|
|
186
|
+
if spec is not None:
|
|
187
|
+
module = import_module(possible)
|
|
188
|
+
break
|
|
120
189
|
|
|
121
|
-
|
|
122
|
-
|
|
190
|
+
if module is None:
|
|
191
|
+
if raise_for_none:
|
|
192
|
+
raise CliError(f"No such module: {possible_modules[0]}")
|
|
193
|
+
return
|
|
123
194
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return op, (args, kwargs)
|
|
130
|
-
except ValueError:
|
|
131
|
-
pass
|
|
132
|
-
|
|
133
|
-
args = [
|
|
134
|
-
_parse_arg(arg)
|
|
135
|
-
for arg in operation_args if '=' not in arg
|
|
136
|
-
]
|
|
137
|
-
|
|
138
|
-
kwargs = {
|
|
139
|
-
key: _parse_arg(value)
|
|
140
|
-
for key, value in [
|
|
141
|
-
arg.split('=', 1)
|
|
142
|
-
for arg in operation_args if '=' in arg
|
|
143
|
-
]
|
|
144
|
-
}
|
|
195
|
+
attr = getattr(module, attr_name, None)
|
|
196
|
+
if attr is None:
|
|
197
|
+
if raise_for_none:
|
|
198
|
+
raise CliError(f"No such attribute in module {possible_modules[0]}: {attr_name}")
|
|
199
|
+
return
|
|
145
200
|
|
|
146
|
-
return
|
|
201
|
+
return attr
|
|
147
202
|
|
|
148
203
|
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
|
|
204
|
+
def _parallel_load_hosts(state: "State", callback: Callable, name: str):
|
|
205
|
+
def load_file(local_host):
|
|
206
|
+
try:
|
|
207
|
+
with ctx_config.use(state.config.copy()):
|
|
208
|
+
with ctx_host.use(local_host):
|
|
209
|
+
callback()
|
|
210
|
+
logger.info(
|
|
211
|
+
"{0}{1} {2}".format(
|
|
212
|
+
local_host.print_prefix,
|
|
213
|
+
click.style("Ready:", "green"),
|
|
214
|
+
click.style(name, bold=True),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
return e
|
|
219
|
+
|
|
220
|
+
greenlet_to_host = {
|
|
221
|
+
state.pool.spawn(load_file, host): host for host in state.inventory.iter_active_hosts()
|
|
222
|
+
}
|
|
152
223
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
224
|
+
with progress_spinner(greenlet_to_host.values()) as progress:
|
|
225
|
+
for greenlet in gevent.iwait(greenlet_to_host.keys()):
|
|
226
|
+
host = greenlet_to_host[greenlet]
|
|
227
|
+
result = greenlet.get()
|
|
228
|
+
if isinstance(result, Exception):
|
|
229
|
+
raise result
|
|
230
|
+
progress(host)
|
|
160
231
|
|
|
161
|
-
pseudo_host.set(host)
|
|
162
232
|
|
|
163
|
-
|
|
233
|
+
def load_deploy_file(state: "State", filename):
|
|
234
|
+
state.current_deploy_filename = filename
|
|
235
|
+
_parallel_load_hosts(state, lambda: exec_file(filename), filename)
|
|
164
236
|
|
|
165
|
-
logger.info('{0} {1} {2}'.format(
|
|
166
|
-
host.print_prefix,
|
|
167
|
-
click.style('Ready:', 'green'),
|
|
168
|
-
click.style(filename, bold=True),
|
|
169
|
-
))
|
|
170
237
|
|
|
171
|
-
|
|
172
|
-
|
|
238
|
+
def load_func(state: "State", op_func, *args, **kwargs):
|
|
239
|
+
_parallel_load_hosts(state, lambda: op_func(*args, **kwargs), op_func.__name__)
|
pyinfra_cli/virtualenv.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
+
import click
|
|
5
|
+
|
|
4
6
|
from pyinfra import logger
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
def init_virtualenv():
|
|
8
|
-
|
|
9
|
+
def init_virtualenv() -> None:
|
|
10
|
+
"""
|
|
9
11
|
Add a virtualenv to sys.path so the user can import modules from it.
|
|
10
12
|
This isn't perfect: it doesn't use the Python interpreter with which the
|
|
11
13
|
virtualenv was built, and it ignores the --no-site-packages option. A
|
|
@@ -14,14 +16,14 @@ def init_virtualenv():
|
|
|
14
16
|
|
|
15
17
|
Adapted from IPython's implementation:
|
|
16
18
|
https://github.com/ipython/ipython/blob/master/IPython/core/interactiveshell.py
|
|
17
|
-
|
|
19
|
+
"""
|
|
18
20
|
|
|
19
|
-
if
|
|
21
|
+
if "VIRTUAL_ENV" not in os.environ:
|
|
20
22
|
# Not in a virtualenv
|
|
21
23
|
return
|
|
22
24
|
|
|
23
25
|
p = os.path.normcase(sys.executable)
|
|
24
|
-
p_venv = os.path.normcase(os.environ[
|
|
26
|
+
p_venv = os.path.normcase(os.environ["VIRTUAL_ENV"])
|
|
25
27
|
|
|
26
28
|
# executable path should end like /bin/python or \\scripts\\python.exe
|
|
27
29
|
p_exe_up2 = os.path.dirname(os.path.dirname(p))
|
|
@@ -39,35 +41,38 @@ def init_virtualenv():
|
|
|
39
41
|
paths.append(p)
|
|
40
42
|
|
|
41
43
|
# In Cygwin paths like 'c:\...' and '\cygdrive\c\...' are possible
|
|
42
|
-
if p_venv.startswith(
|
|
44
|
+
if p_venv.startswith("\\cygdrive"):
|
|
43
45
|
p_venv = p_venv[11:]
|
|
44
|
-
elif len(p_venv) >= 2 and p_venv[1] ==
|
|
46
|
+
elif len(p_venv) >= 2 and p_venv[1] == ":":
|
|
45
47
|
p_venv = p_venv[2:]
|
|
46
48
|
|
|
47
49
|
if any(p_venv in p for p in paths):
|
|
48
50
|
# Running properly in the virtualenv, don't need to do anything
|
|
49
51
|
return
|
|
50
52
|
|
|
51
|
-
logger.warning(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
logger.warning(
|
|
54
|
+
(
|
|
55
|
+
"Attempting to work in a virtualenv.\n"
|
|
56
|
+
" If you encounter problems, please install pyinfra inside the virtualenv."
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
click.echo(err=True)
|
|
56
60
|
|
|
57
|
-
if sys.platform ==
|
|
61
|
+
if sys.platform == "win32":
|
|
58
62
|
virtual_env = os.path.join(
|
|
59
|
-
os.environ[
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
os.environ["VIRTUAL_ENV"],
|
|
64
|
+
"Lib",
|
|
65
|
+
"site-packages",
|
|
62
66
|
)
|
|
63
67
|
else:
|
|
64
68
|
virtual_env = os.path.join(
|
|
65
|
-
os.environ[
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
os.environ["VIRTUAL_ENV"],
|
|
70
|
+
"lib",
|
|
71
|
+
"python%d.%d" % sys.version_info[:2],
|
|
72
|
+
"site-packages",
|
|
69
73
|
)
|
|
70
74
|
|
|
71
75
|
import site
|
|
76
|
+
|
|
72
77
|
sys.path.insert(0, virtual_env)
|
|
73
78
|
site.addsitedir(virtual_env)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from . import ansible, docker, local, ssh, vagrant
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
# Connectors that handle execution of pyinfra operations
|
|
5
|
-
EXECUTION_CONNECTORS = { # pragma: no cover
|
|
6
|
-
'docker': docker,
|
|
7
|
-
'local': local,
|
|
8
|
-
'ssh': ssh,
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
# Connectors that handle generation of inventories
|
|
12
|
-
INVENTORY_CONNECTORS = { # pragma: no cover
|
|
13
|
-
'docker': docker,
|
|
14
|
-
'vagrant': vagrant,
|
|
15
|
-
'ansible': ansible,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
ALL_CONNECTORS = ( # pragma: no cover
|
|
19
|
-
list(EXECUTION_CONNECTORS.keys())
|
|
20
|
-
+ list(INVENTORY_CONNECTORS.keys())
|
|
21
|
-
)
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from collections import defaultdict
|
|
4
|
-
from configparser import ConfigParser
|
|
5
|
-
from os import path
|
|
6
|
-
|
|
7
|
-
from pyinfra import logger
|
|
8
|
-
from pyinfra.api.exceptions import InventoryError
|
|
9
|
-
from pyinfra.api.util import memoize
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@memoize
|
|
13
|
-
def show_warning():
|
|
14
|
-
logger.warning('The @ansible connector is in Alpha!')
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@memoize
|
|
18
|
-
def get_ansible_inventory(inventory_filename=None):
|
|
19
|
-
if not inventory_filename: # pragma: no cover
|
|
20
|
-
raise InventoryError('No Ansible inventory filename provided!')
|
|
21
|
-
|
|
22
|
-
if not path.exists(inventory_filename):
|
|
23
|
-
raise InventoryError((
|
|
24
|
-
'Could not find Ansible inventory file: {0}'
|
|
25
|
-
).format(inventory_filename))
|
|
26
|
-
|
|
27
|
-
logger.info('Parsing Ansible inventory...')
|
|
28
|
-
|
|
29
|
-
config = ConfigParser(
|
|
30
|
-
delimiters=(' '), # we only handle the hostnames for now
|
|
31
|
-
allow_no_value=True, # we don't by default have = values
|
|
32
|
-
interpolation=None, # remove any Python interpolation
|
|
33
|
-
)
|
|
34
|
-
config.read(inventory_filename)
|
|
35
|
-
return config
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _parse_ansible_hosts(hosts):
|
|
39
|
-
for host in hosts:
|
|
40
|
-
expand_match = re.search(r'\[[0-9:]+\]', host)
|
|
41
|
-
if expand_match:
|
|
42
|
-
expand_string = host[expand_match.start():expand_match.end()]
|
|
43
|
-
bits = expand_string[1:-1].split(':') # remove the [] either side
|
|
44
|
-
|
|
45
|
-
zfill = 0
|
|
46
|
-
if bits[0].startswith('0'):
|
|
47
|
-
zfill = len(bits[0])
|
|
48
|
-
|
|
49
|
-
start, end = int(bits[0]), int(bits[1])
|
|
50
|
-
step = int(bits[2]) if len(bits) > 2 else 1
|
|
51
|
-
|
|
52
|
-
for n in range(start, end + 1, step):
|
|
53
|
-
number_as_string = '{0}'.format(n)
|
|
54
|
-
if zfill:
|
|
55
|
-
number_as_string = number_as_string.zfill(zfill)
|
|
56
|
-
|
|
57
|
-
hostname = host.replace(expand_string, number_as_string)
|
|
58
|
-
yield hostname
|
|
59
|
-
else:
|
|
60
|
-
yield host
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def make_names_data(inventory_filename=None):
|
|
64
|
-
show_warning()
|
|
65
|
-
|
|
66
|
-
config = get_ansible_inventory(inventory_filename)
|
|
67
|
-
|
|
68
|
-
host_to_groups = defaultdict(set)
|
|
69
|
-
group_to_hosts = defaultdict(set)
|
|
70
|
-
hosts = []
|
|
71
|
-
|
|
72
|
-
# First pass - load hosts/groups of hosts
|
|
73
|
-
for section in config.sections():
|
|
74
|
-
if ':' in section: # ignore :children and :vars sections this time
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
options = config.options(section)
|
|
78
|
-
for host in _parse_ansible_hosts(options):
|
|
79
|
-
hosts.append(host)
|
|
80
|
-
host_to_groups[host].add(section)
|
|
81
|
-
group_to_hosts[section].add(host)
|
|
82
|
-
|
|
83
|
-
# Second pass - load any children groups
|
|
84
|
-
for section in config.sections():
|
|
85
|
-
if not section.endswith(':children'): # we only support :children for now
|
|
86
|
-
continue
|
|
87
|
-
|
|
88
|
-
group_name = section.replace(':children', '')
|
|
89
|
-
|
|
90
|
-
options = config.options(section)
|
|
91
|
-
for sub_group_name in options:
|
|
92
|
-
sub_group_hosts = group_to_hosts[sub_group_name]
|
|
93
|
-
for host in sub_group_hosts:
|
|
94
|
-
host_to_groups[host].add(group_name)
|
|
95
|
-
|
|
96
|
-
return [
|
|
97
|
-
(host, {}, sorted(list(host_to_groups.get(host))))
|
|
98
|
-
for host in hosts
|
|
99
|
-
]
|