pyinfra 3.0b0__py2.py3-none-any.whl → 3.0b1__py2.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/api/__init__.py +3 -0
- pyinfra/api/arguments.py +5 -4
- pyinfra/api/arguments_typed.py +12 -2
- pyinfra/api/exceptions.py +19 -0
- pyinfra/api/facts.py +1 -1
- pyinfra/api/host.py +46 -7
- pyinfra/api/operation.py +77 -39
- pyinfra/api/operations.py +10 -11
- pyinfra/api/state.py +11 -2
- pyinfra/connectors/base.py +1 -1
- pyinfra/connectors/chroot.py +5 -6
- pyinfra/connectors/docker.py +11 -10
- pyinfra/connectors/dockerssh.py +5 -4
- pyinfra/connectors/local.py +5 -5
- pyinfra/connectors/ssh.py +44 -23
- pyinfra/connectors/terraform.py +9 -6
- pyinfra/connectors/util.py +1 -1
- pyinfra/connectors/vagrant.py +6 -5
- pyinfra/facts/choco.py +1 -1
- pyinfra/facts/deb.py +2 -2
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +5 -164
- pyinfra/facts/systemd.py +26 -10
- pyinfra/operations/files.py +5 -3
- pyinfra/operations/iptables.py +6 -0
- pyinfra/operations/pip.py +5 -0
- pyinfra/operations/postgres.py +347 -0
- pyinfra/operations/postgresql.py +17 -336
- pyinfra/operations/systemd.py +5 -3
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b1.dist-info}/METADATA +6 -6
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b1.dist-info}/RECORD +44 -43
- pyinfra_cli/commands.py +3 -2
- pyinfra_cli/exceptions.py +5 -0
- pyinfra_cli/main.py +2 -0
- pyinfra_cli/prints.py +22 -104
- tests/test_api/test_api_deploys.py +5 -5
- tests/test_api/test_api_operations.py +4 -4
- tests/test_connectors/test_ssh.py +52 -0
- tests/test_connectors/test_terraform.py +11 -8
- tests/test_connectors/test_vagrant.py +3 -3
- pyinfra_cli/inventory_dsl.py +0 -23
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b1.dist-info}/entry_points.txt +0 -0
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
pyinfra_cli/prints.py
CHANGED
|
@@ -217,7 +217,7 @@ def pretty_op_name(op_meta):
|
|
|
217
217
|
|
|
218
218
|
def print_meta(state: "State"):
|
|
219
219
|
rows: List[Tuple[Callable, Union[List[str], str]]] = [
|
|
220
|
-
(logger.info, ["Operation", "
|
|
220
|
+
(logger.info, ["Operation", "Change", "Conditional Change"]),
|
|
221
221
|
]
|
|
222
222
|
|
|
223
223
|
for op_hash in state.get_op_order():
|
|
@@ -237,14 +237,18 @@ def print_meta(state: "State"):
|
|
|
237
237
|
logger.info,
|
|
238
238
|
[
|
|
239
239
|
pretty_op_name(state.op_meta[op_hash]),
|
|
240
|
-
"
|
|
240
|
+
"-"
|
|
241
|
+
if len(hosts_in_op) == 0
|
|
242
|
+
else "{0} ({1})".format(
|
|
241
243
|
len(hosts_in_op),
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
truncate(", ".join(sorted(hosts_in_op)), 48),
|
|
245
|
+
),
|
|
246
|
+
"-"
|
|
247
|
+
if len(hosts_maybe_in_op) == 0
|
|
248
|
+
else "{0} ({1})".format(
|
|
249
|
+
len(hosts_maybe_in_op),
|
|
250
|
+
truncate(", ".join(sorted(hosts_maybe_in_op)), 48),
|
|
251
|
+
),
|
|
248
252
|
],
|
|
249
253
|
)
|
|
250
254
|
)
|
|
@@ -261,23 +265,21 @@ def print_results(state: "State"):
|
|
|
261
265
|
hosts_in_op = 0
|
|
262
266
|
hosts_in_op_success: list[str] = []
|
|
263
267
|
hosts_in_op_error: list[str] = []
|
|
264
|
-
|
|
268
|
+
hosts_in_op_no_change: list[str] = []
|
|
265
269
|
for host in state.inventory.iter_activated_hosts():
|
|
266
270
|
if op_hash not in state.ops[host]:
|
|
267
271
|
continue
|
|
268
272
|
|
|
269
273
|
hosts_in_op += 1
|
|
270
274
|
|
|
271
|
-
|
|
272
|
-
if
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
op_meta = state.ops[host][op_hash].operation_meta
|
|
276
|
+
if op_meta.did_succeed():
|
|
277
|
+
if op_meta._did_change():
|
|
278
|
+
hosts_in_op_success.append(host.name)
|
|
279
|
+
else:
|
|
280
|
+
hosts_in_op_no_change.append(host.name)
|
|
276
281
|
else:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
# if not hosts_in_op:
|
|
280
|
-
# continue
|
|
282
|
+
hosts_in_op_error.append(host.name)
|
|
281
283
|
|
|
282
284
|
row = [
|
|
283
285
|
pretty_op_name(state.op_meta[op_hash]),
|
|
@@ -292,95 +294,11 @@ def print_results(state: "State"):
|
|
|
292
294
|
row.append(f"{len(hosts_in_op_error)}")
|
|
293
295
|
else:
|
|
294
296
|
row.append("-")
|
|
295
|
-
if
|
|
296
|
-
row.append(f"{len(
|
|
297
|
+
if hosts_in_op_no_change:
|
|
298
|
+
row.append(f"{len(hosts_in_op_no_change)}")
|
|
297
299
|
else:
|
|
298
300
|
row.append("-")
|
|
299
301
|
|
|
300
302
|
rows.append((logger.info, row))
|
|
301
303
|
|
|
302
304
|
print_rows(rows)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
def get_fucked(state: "State"):
|
|
306
|
-
group_combinations = _get_group_combinations(state.inventory.iter_activated_hosts())
|
|
307
|
-
rows: List[Tuple[Callable, Union[List[str], str]]] = []
|
|
308
|
-
|
|
309
|
-
for i, (groups, hosts) in enumerate(group_combinations.items(), 1):
|
|
310
|
-
if not hosts:
|
|
311
|
-
continue
|
|
312
|
-
|
|
313
|
-
if groups:
|
|
314
|
-
rows.append(
|
|
315
|
-
(
|
|
316
|
-
logger.info,
|
|
317
|
-
"Groups: {0}".format(
|
|
318
|
-
click.style(" / ".join(groups), bold=True),
|
|
319
|
-
),
|
|
320
|
-
),
|
|
321
|
-
)
|
|
322
|
-
else:
|
|
323
|
-
rows.append((logger.info, "Ungrouped:"))
|
|
324
|
-
|
|
325
|
-
for host in hosts:
|
|
326
|
-
# Didn't connect to this host?
|
|
327
|
-
if host not in state.activated_hosts:
|
|
328
|
-
rows.append(
|
|
329
|
-
(
|
|
330
|
-
logger.info,
|
|
331
|
-
[
|
|
332
|
-
host.style_print_prefix("red", bold=True),
|
|
333
|
-
click.style("No connection", "red"),
|
|
334
|
-
],
|
|
335
|
-
),
|
|
336
|
-
)
|
|
337
|
-
continue
|
|
338
|
-
|
|
339
|
-
results = state.results[host]
|
|
340
|
-
|
|
341
|
-
meta = state.meta[host]
|
|
342
|
-
success_ops = results.success_ops
|
|
343
|
-
partial_ops = results.partial_ops
|
|
344
|
-
# TODO: type meta object
|
|
345
|
-
changed_ops = success_ops - meta.ops_no_change # type: ignore
|
|
346
|
-
error_ops = results.error_ops
|
|
347
|
-
ignored_error_ops = results.ignored_error_ops
|
|
348
|
-
|
|
349
|
-
host_args = ("green",)
|
|
350
|
-
host_kwargs = {}
|
|
351
|
-
|
|
352
|
-
# If all ops got complete
|
|
353
|
-
if results.ops == meta.ops:
|
|
354
|
-
# We had some errors - but we ignored them - so "warning" color
|
|
355
|
-
if error_ops != 0:
|
|
356
|
-
host_args = ("yellow",)
|
|
357
|
-
|
|
358
|
-
# Ops did not complete!
|
|
359
|
-
else:
|
|
360
|
-
host_args = ("red",)
|
|
361
|
-
host_kwargs["bold"] = True
|
|
362
|
-
|
|
363
|
-
changed_str = "Changed: {0}".format(click.style(f"{changed_ops}", bold=True))
|
|
364
|
-
if partial_ops:
|
|
365
|
-
changed_str = f"{changed_str} ({partial_ops} partial)"
|
|
366
|
-
|
|
367
|
-
error_str = "Errors: {0}".format(click.style(f"{error_ops}", bold=True))
|
|
368
|
-
if ignored_error_ops:
|
|
369
|
-
error_str = f"{error_str} ({ignored_error_ops} ignored)"
|
|
370
|
-
|
|
371
|
-
rows.append(
|
|
372
|
-
(
|
|
373
|
-
logger.info,
|
|
374
|
-
[
|
|
375
|
-
host.style_print_prefix(*host_args, **host_kwargs),
|
|
376
|
-
changed_str,
|
|
377
|
-
"No change: {0}".format(click.style(f"{meta.ops_no_change}", bold=True)),
|
|
378
|
-
error_str,
|
|
379
|
-
],
|
|
380
|
-
),
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
if i != len(group_combinations):
|
|
384
|
-
rows.append((lambda m: click.echo(m, err=True), []))
|
|
385
|
-
|
|
386
|
-
print_rows(rows)
|
|
@@ -40,7 +40,7 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
40
40
|
run_ops(state)
|
|
41
41
|
|
|
42
42
|
first_op_hash = op_order[0]
|
|
43
|
-
assert state.op_meta[first_op_hash].names == {"test_deploy |
|
|
43
|
+
assert state.op_meta[first_op_hash].names == {"test_deploy | server.shell"}
|
|
44
44
|
assert state.ops[somehost][first_op_hash].operation_meta._commands == [
|
|
45
45
|
StringCommand("echo first command"),
|
|
46
46
|
]
|
|
@@ -49,7 +49,7 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
49
49
|
]
|
|
50
50
|
|
|
51
51
|
second_op_hash = op_order[1]
|
|
52
|
-
assert state.op_meta[second_op_hash].names == {"test_deploy |
|
|
52
|
+
assert state.op_meta[second_op_hash].names == {"test_deploy | server.shell"}
|
|
53
53
|
assert state.ops[somehost][second_op_hash].operation_meta._commands == [
|
|
54
54
|
StringCommand("echo second command"),
|
|
55
55
|
]
|
|
@@ -104,21 +104,21 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
104
104
|
run_ops(state)
|
|
105
105
|
|
|
106
106
|
first_op_hash = op_order[0]
|
|
107
|
-
assert state.op_meta[first_op_hash].names == {"test_deploy |
|
|
107
|
+
assert state.op_meta[first_op_hash].names == {"test_deploy | server.shell"}
|
|
108
108
|
assert state.ops[somehost][first_op_hash].operation_meta._commands == [
|
|
109
109
|
StringCommand("echo first command"),
|
|
110
110
|
]
|
|
111
111
|
|
|
112
112
|
second_op_hash = op_order[1]
|
|
113
113
|
assert state.op_meta[second_op_hash].names == {
|
|
114
|
-
"test_deploy | test_nested_deploy |
|
|
114
|
+
"test_deploy | test_nested_deploy | server.shell",
|
|
115
115
|
}
|
|
116
116
|
assert state.ops[somehost][second_op_hash].operation_meta._commands == [
|
|
117
117
|
StringCommand("echo nested command"),
|
|
118
118
|
]
|
|
119
119
|
|
|
120
120
|
third_op_hash = op_order[2]
|
|
121
|
-
assert state.op_meta[third_op_hash].names == {"test_deploy |
|
|
121
|
+
assert state.op_meta[third_op_hash].names == {"test_deploy | server.shell"}
|
|
122
122
|
assert state.ops[somehost][third_op_hash].operation_meta._commands == [
|
|
123
123
|
StringCommand("echo second command"),
|
|
124
124
|
]
|
|
@@ -78,7 +78,7 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
78
78
|
first_op_hash = op_order[0]
|
|
79
79
|
|
|
80
80
|
# Ensure the op name
|
|
81
|
-
assert state.op_meta[first_op_hash].names == {"
|
|
81
|
+
assert state.op_meta[first_op_hash].names == {"files.file"}
|
|
82
82
|
|
|
83
83
|
# Ensure the global kwargs (same for both hosts)
|
|
84
84
|
somehost_global_arguments = state.ops[somehost][first_op_hash].global_arguments
|
|
@@ -433,11 +433,11 @@ class TestNestedOperationsApi(PatchSSHTestCase):
|
|
|
433
433
|
|
|
434
434
|
try:
|
|
435
435
|
outer_result = server.shell(commands="echo outer")
|
|
436
|
-
assert outer_result.
|
|
436
|
+
assert outer_result._combined_output is None
|
|
437
437
|
|
|
438
438
|
def callback():
|
|
439
439
|
inner_result = server.shell(commands="echo inner")
|
|
440
|
-
assert inner_result.
|
|
440
|
+
assert inner_result._combined_output is not None
|
|
441
441
|
|
|
442
442
|
python.call(function=callback)
|
|
443
443
|
|
|
@@ -447,7 +447,7 @@ class TestNestedOperationsApi(PatchSSHTestCase):
|
|
|
447
447
|
|
|
448
448
|
assert len(state.get_op_order()) == 3
|
|
449
449
|
assert state.results[somehost].success_ops == 3
|
|
450
|
-
assert outer_result.
|
|
450
|
+
assert outer_result._combined_output is not None
|
|
451
451
|
|
|
452
452
|
disconnect_all(state)
|
|
453
453
|
finally:
|
|
@@ -1001,3 +1001,55 @@ class TestSSHConnector(TestCase):
|
|
|
1001
1001
|
"not-another-file",
|
|
1002
1002
|
print_output=True,
|
|
1003
1003
|
)
|
|
1004
|
+
|
|
1005
|
+
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
1006
|
+
@patch("time.sleep")
|
|
1007
|
+
def test_ssh_connect_fail_retry(self, fake_sleep, fake_ssh_client):
|
|
1008
|
+
for exception_class in (
|
|
1009
|
+
SSHException,
|
|
1010
|
+
gaierror,
|
|
1011
|
+
socket_error,
|
|
1012
|
+
EOFError,
|
|
1013
|
+
):
|
|
1014
|
+
inventory = make_inventory(
|
|
1015
|
+
hosts=("unresposivehost",), override_data={"ssh_connect_retries": 1}
|
|
1016
|
+
)
|
|
1017
|
+
State(inventory, Config())
|
|
1018
|
+
|
|
1019
|
+
unresposivehost = inventory.get_host("unresposivehost")
|
|
1020
|
+
assert unresposivehost.data.ssh_connect_retries == 1
|
|
1021
|
+
|
|
1022
|
+
fake_ssh = MagicMock()
|
|
1023
|
+
fake_ssh.connect.side_effect = exception_class()
|
|
1024
|
+
fake_ssh_client.return_value = fake_ssh
|
|
1025
|
+
|
|
1026
|
+
with self.assertRaises(ConnectError):
|
|
1027
|
+
unresposivehost.connect(show_errors=False, raise_exceptions=True)
|
|
1028
|
+
assert fake_sleep.called_once()
|
|
1029
|
+
assert fake_ssh_client.connect.called_twice()
|
|
1030
|
+
|
|
1031
|
+
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
1032
|
+
@patch("time.sleep")
|
|
1033
|
+
def test_ssh_connect_fail_success(self, fake_sleep, fake_ssh_client):
|
|
1034
|
+
for exception_class in (
|
|
1035
|
+
SSHException,
|
|
1036
|
+
gaierror,
|
|
1037
|
+
socket_error,
|
|
1038
|
+
EOFError,
|
|
1039
|
+
):
|
|
1040
|
+
inventory = make_inventory(
|
|
1041
|
+
hosts=("unresposivehost",), override_data={"ssh_connect_retries": 1}
|
|
1042
|
+
)
|
|
1043
|
+
State(inventory, Config())
|
|
1044
|
+
|
|
1045
|
+
unresposivehost = inventory.get_host("unresposivehost")
|
|
1046
|
+
assert unresposivehost.data.ssh_connect_retries == 1
|
|
1047
|
+
|
|
1048
|
+
connection = MagicMock()
|
|
1049
|
+
fake_ssh = MagicMock()
|
|
1050
|
+
fake_ssh.connect.side_effect = [exception_class(), connection]
|
|
1051
|
+
fake_ssh_client.return_value = fake_ssh
|
|
1052
|
+
|
|
1053
|
+
unresposivehost.connect(show_errors=False, raise_exceptions=True)
|
|
1054
|
+
assert fake_sleep.called_once()
|
|
1055
|
+
assert fake_ssh_client.connect.called_twice()
|
|
@@ -7,20 +7,23 @@ from pyinfra.connectors.terraform import TerraformInventoryConnector
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TestTerraformConnector(TestCase):
|
|
10
|
-
def test_make_names_data_no_output_key(self):
|
|
11
|
-
with self.assertRaises(InventoryError) as context:
|
|
12
|
-
list(TerraformInventoryConnector.make_names_data())
|
|
13
|
-
|
|
14
|
-
assert context.exception.args[0] == "No Terraform output key!"
|
|
15
|
-
|
|
16
10
|
@patch("pyinfra.connectors.terraform.local.shell")
|
|
17
11
|
def test_make_names_data_no_output(self, fake_shell):
|
|
18
|
-
fake_shell.return_value = json.dumps(
|
|
12
|
+
fake_shell.return_value = json.dumps(
|
|
13
|
+
{
|
|
14
|
+
"hello": {
|
|
15
|
+
"world": [],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
)
|
|
19
19
|
|
|
20
20
|
with self.assertRaises(InventoryError) as context:
|
|
21
21
|
list(TerraformInventoryConnector.make_names_data("output_key"))
|
|
22
22
|
|
|
23
|
-
assert
|
|
23
|
+
assert (
|
|
24
|
+
context.exception.args[0]
|
|
25
|
+
== "No Terraform output with key: `output_key`, valid keys:\n - hello.world"
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
@patch("pyinfra.connectors.terraform.local.shell")
|
|
26
29
|
def test_make_names_data_invalid_output(self, fake_shell):
|
|
@@ -69,7 +69,7 @@ class TestVagrantConnector(TestCase):
|
|
|
69
69
|
)
|
|
70
70
|
@patch("pyinfra.connectors.vagrant.path.exists", lambda path: True)
|
|
71
71
|
def test_make_names_data_with_options(self):
|
|
72
|
-
data = VagrantInventoryConnector.make_names_data()
|
|
72
|
+
data = list(VagrantInventoryConnector.make_names_data())
|
|
73
73
|
|
|
74
74
|
assert data == [
|
|
75
75
|
(
|
|
@@ -103,7 +103,7 @@ class TestVagrantConnector(TestCase):
|
|
|
103
103
|
]
|
|
104
104
|
|
|
105
105
|
def test_make_names_data_with_limit(self):
|
|
106
|
-
data = VagrantInventoryConnector.make_names_data(
|
|
106
|
+
data = list(VagrantInventoryConnector.make_names_data(name=("ubuntu16",)))
|
|
107
107
|
|
|
108
108
|
assert data == [
|
|
109
109
|
(
|
|
@@ -120,4 +120,4 @@ class TestVagrantConnector(TestCase):
|
|
|
120
120
|
|
|
121
121
|
def test_make_names_data_no_matches(self):
|
|
122
122
|
with self.assertRaises(InventoryError):
|
|
123
|
-
VagrantInventoryConnector.make_names_data(
|
|
123
|
+
list(VagrantInventoryConnector.make_names_data(name="nope"))
|
pyinfra_cli/inventory_dsl.py
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
class Host:
|
|
2
|
-
name: str
|
|
3
|
-
data: dict
|
|
4
|
-
|
|
5
|
-
def __init__(self, name, **data) -> None:
|
|
6
|
-
self.name = name
|
|
7
|
-
self.data = data
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Group:
|
|
11
|
-
hosts: list[Host]
|
|
12
|
-
data: dict
|
|
13
|
-
|
|
14
|
-
def __init__(self, *hosts: Host, **data) -> None:
|
|
15
|
-
self.hosts = list(hosts)
|
|
16
|
-
self.data = data
|
|
17
|
-
|
|
18
|
-
def __iter__(self):
|
|
19
|
-
for host in self.hosts:
|
|
20
|
-
yield host
|
|
21
|
-
|
|
22
|
-
def append(self, *hosts: Host) -> None:
|
|
23
|
-
self.hosts.extend(hosts)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|