pyinfra 2.9.2__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 +261 -255
- pyinfra/api/arguments_typed.py +77 -0
- pyinfra/api/command.py +66 -53
- pyinfra/api/config.py +27 -22
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +2 -24
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +77 -113
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +17 -25
- pyinfra/api/operation.py +232 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +55 -70
- pyinfra/connectors/base.py +150 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +227 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +195 -207
- pyinfra/connectors/ssh.py +528 -615
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +212 -137
- pyinfra/connectors/vagrant.py +55 -48
- pyinfra/context.py +3 -2
- pyinfra/facts/docker.py +1 -0
- pyinfra/facts/files.py +45 -32
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +1 -1
- pyinfra/facts/hardware.py +4 -2
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/mysql.py +1 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +5 -161
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +77 -30
- pyinfra/facts/systemd.py +29 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/util/packaging.py +4 -2
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +3 -3
- pyinfra/operations/apt.py +25 -47
- pyinfra/operations/brew.py +7 -14
- pyinfra/operations/bsdinit.py +4 -4
- pyinfra/operations/cargo.py +1 -1
- pyinfra/operations/choco.py +1 -1
- pyinfra/operations/dnf.py +4 -4
- pyinfra/operations/files.py +108 -321
- pyinfra/operations/gem.py +1 -1
- pyinfra/operations/git.py +6 -37
- pyinfra/operations/iptables.py +2 -10
- pyinfra/operations/launchd.py +1 -1
- pyinfra/operations/lxd.py +1 -9
- pyinfra/operations/mysql.py +5 -28
- pyinfra/operations/npm.py +1 -1
- pyinfra/operations/openrc.py +1 -1
- pyinfra/operations/pacman.py +3 -3
- pyinfra/operations/pip.py +14 -15
- pyinfra/operations/pkg.py +1 -1
- pyinfra/operations/pkgin.py +3 -3
- pyinfra/operations/postgres.py +347 -0
- pyinfra/operations/postgresql.py +17 -380
- pyinfra/operations/python.py +2 -17
- pyinfra/operations/selinux.py +5 -28
- pyinfra/operations/server.py +59 -84
- pyinfra/operations/snap.py +1 -3
- pyinfra/operations/ssh.py +8 -23
- pyinfra/operations/systemd.py +7 -7
- pyinfra/operations/sysvinit.py +3 -12
- pyinfra/operations/upstart.py +4 -4
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/files.py +2 -2
- pyinfra/operations/util/packaging.py +6 -24
- pyinfra/operations/util/service.py +18 -37
- pyinfra/operations/vzctl.py +2 -2
- pyinfra/operations/xbps.py +3 -3
- pyinfra/operations/yum.py +4 -4
- pyinfra/operations/zypper.py +4 -4
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
- pyinfra-3.0b1.dist-info/RECORD +163 -0
- pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +2 -0
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +83 -42
- pyinfra_cli/inventory.py +19 -4
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +93 -129
- pyinfra_cli/util.py +60 -29
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +13 -13
- tests/test_api/test_api_deploys.py +28 -29
- tests/test_api/test_api_facts.py +60 -98
- tests/test_api/test_api_operations.py +100 -200
- tests/test_cli/test_cli.py +18 -49
- tests/test_cli/test_cli_deploy.py +11 -37
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_chroot.py +6 -6
- tests/test_connectors/test_docker.py +4 -4
- tests/test_connectors/test_dockerssh.py +38 -50
- tests/test_connectors/test_local.py +11 -12
- tests/test_connectors/test_ssh.py +66 -107
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +4 -4
- pyinfra/api/operation.pyi +0 -117
- pyinfra/connectors/ansible.py +0 -171
- pyinfra/connectors/mech.py +0 -186
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -320
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -551
- pyinfra-2.9.2.dist-info/RECORD +0 -170
- pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- tests/test_connectors/test_winrm.py +0 -76
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
pyinfra/facts/server.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
import re
|
|
3
5
|
import shutil
|
|
4
6
|
from datetime import datetime
|
|
5
7
|
from tempfile import mkdtemp
|
|
8
|
+
from typing import Dict, List, NewType, Optional, Union
|
|
6
9
|
|
|
7
10
|
from dateutil.parser import parse as parse_date
|
|
8
11
|
from distro import distro
|
|
12
|
+
from typing_extensions import NotRequired, TypedDict
|
|
9
13
|
|
|
10
14
|
from pyinfra.api import FactBase, ShortFactBase
|
|
11
15
|
from pyinfra.api.util import try_int
|
|
@@ -37,6 +41,14 @@ class Path(FactBase):
|
|
|
37
41
|
command = "echo $PATH"
|
|
38
42
|
|
|
39
43
|
|
|
44
|
+
class TmpDir(FactBase):
|
|
45
|
+
"""
|
|
46
|
+
Returns the temporary directory of the current server, if configured.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
command = "echo $TMPDIR"
|
|
50
|
+
|
|
51
|
+
|
|
40
52
|
class Hostname(FactBase):
|
|
41
53
|
"""
|
|
42
54
|
Returns the current hostname of the server.
|
|
@@ -62,7 +74,7 @@ class KernelVersion(FactBase):
|
|
|
62
74
|
|
|
63
75
|
|
|
64
76
|
# Deprecated/renamed -> Kernel
|
|
65
|
-
class Os(FactBase):
|
|
77
|
+
class Os(FactBase[str]):
|
|
66
78
|
"""
|
|
67
79
|
Returns the OS name according to ``uname``.
|
|
68
80
|
|
|
@@ -74,7 +86,7 @@ class Os(FactBase):
|
|
|
74
86
|
|
|
75
87
|
|
|
76
88
|
# Deprecated/renamed -> KernelVersion
|
|
77
|
-
class OsVersion(FactBase):
|
|
89
|
+
class OsVersion(FactBase[str]):
|
|
78
90
|
"""
|
|
79
91
|
Returns the OS version according to ``uname``.
|
|
80
92
|
|
|
@@ -85,7 +97,7 @@ class OsVersion(FactBase):
|
|
|
85
97
|
command = "uname -r"
|
|
86
98
|
|
|
87
99
|
|
|
88
|
-
class Arch(FactBase):
|
|
100
|
+
class Arch(FactBase[str]):
|
|
89
101
|
"""
|
|
90
102
|
Returns the system architecture according to ``uname``.
|
|
91
103
|
"""
|
|
@@ -95,7 +107,7 @@ class Arch(FactBase):
|
|
|
95
107
|
command = "uname -m"
|
|
96
108
|
|
|
97
109
|
|
|
98
|
-
class Command(FactBase):
|
|
110
|
+
class Command(FactBase[str]):
|
|
99
111
|
"""
|
|
100
112
|
Returns the raw output lines of a given command.
|
|
101
113
|
"""
|
|
@@ -105,7 +117,7 @@ class Command(FactBase):
|
|
|
105
117
|
return command
|
|
106
118
|
|
|
107
119
|
|
|
108
|
-
class Which(FactBase):
|
|
120
|
+
class Which(FactBase[Optional[str]]):
|
|
109
121
|
"""
|
|
110
122
|
Returns the path of a given command, if available.
|
|
111
123
|
"""
|
|
@@ -115,7 +127,7 @@ class Which(FactBase):
|
|
|
115
127
|
return "which {0} || true".format(command)
|
|
116
128
|
|
|
117
129
|
|
|
118
|
-
class Date(FactBase):
|
|
130
|
+
class Date(FactBase[datetime]):
|
|
119
131
|
"""
|
|
120
132
|
Returns the current datetime on the server.
|
|
121
133
|
"""
|
|
@@ -124,11 +136,11 @@ class Date(FactBase):
|
|
|
124
136
|
default = datetime.now
|
|
125
137
|
|
|
126
138
|
@staticmethod
|
|
127
|
-
def process(output):
|
|
139
|
+
def process(output) -> datetime:
|
|
128
140
|
return datetime.strptime(output[0], ISO_DATE_FORMAT)
|
|
129
141
|
|
|
130
142
|
|
|
131
|
-
class MacosVersion(FactBase):
|
|
143
|
+
class MacosVersion(FactBase[str]):
|
|
132
144
|
"""
|
|
133
145
|
Returns the installed MacOS version.
|
|
134
146
|
"""
|
|
@@ -137,7 +149,13 @@ class MacosVersion(FactBase):
|
|
|
137
149
|
requires_command = "sw_vers"
|
|
138
150
|
|
|
139
151
|
|
|
140
|
-
class
|
|
152
|
+
class MountsDict(TypedDict):
|
|
153
|
+
device: str
|
|
154
|
+
type: str
|
|
155
|
+
options: list[str]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Mounts(FactBase[Dict[str, MountsDict]]):
|
|
141
159
|
"""
|
|
142
160
|
Returns a dictionary of mounted filesystems and information.
|
|
143
161
|
|
|
@@ -159,8 +177,8 @@ class Mounts(FactBase):
|
|
|
159
177
|
default = dict
|
|
160
178
|
|
|
161
179
|
@staticmethod
|
|
162
|
-
def process(output):
|
|
163
|
-
devices = {}
|
|
180
|
+
def process(output) -> dict[str, MountsDict]:
|
|
181
|
+
devices: dict[str, MountsDict] = {}
|
|
164
182
|
|
|
165
183
|
for line in output:
|
|
166
184
|
is_map = False
|
|
@@ -285,9 +303,14 @@ class Sysctl(FactBase):
|
|
|
285
303
|
}
|
|
286
304
|
"""
|
|
287
305
|
|
|
288
|
-
command = "sysctl -a"
|
|
289
306
|
default = dict
|
|
290
307
|
|
|
308
|
+
@staticmethod
|
|
309
|
+
def command(keys=None):
|
|
310
|
+
if keys is None:
|
|
311
|
+
return "sysctl -a"
|
|
312
|
+
return f"sysctl {' '.join(keys)}"
|
|
313
|
+
|
|
291
314
|
@staticmethod
|
|
292
315
|
def process(output):
|
|
293
316
|
sysctls = {}
|
|
@@ -317,7 +340,7 @@ class Sysctl(FactBase):
|
|
|
317
340
|
return sysctls
|
|
318
341
|
|
|
319
342
|
|
|
320
|
-
class Groups(FactBase):
|
|
343
|
+
class Groups(FactBase[List[str]]):
|
|
321
344
|
"""
|
|
322
345
|
Returns a list of groups on the system.
|
|
323
346
|
"""
|
|
@@ -326,8 +349,8 @@ class Groups(FactBase):
|
|
|
326
349
|
default = list
|
|
327
350
|
|
|
328
351
|
@staticmethod
|
|
329
|
-
def process(output):
|
|
330
|
-
groups = []
|
|
352
|
+
def process(output) -> list[str]:
|
|
353
|
+
groups: list[str] = []
|
|
331
354
|
|
|
332
355
|
for line in output:
|
|
333
356
|
if ":" in line:
|
|
@@ -336,7 +359,20 @@ class Groups(FactBase):
|
|
|
336
359
|
return groups
|
|
337
360
|
|
|
338
361
|
|
|
339
|
-
|
|
362
|
+
CrontabCommand = NewType("CrontabCommand", int)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class CrontabDict(TypedDict):
|
|
366
|
+
minute: NotRequired[Union[int, str]]
|
|
367
|
+
hour: NotRequired[Union[int, str]]
|
|
368
|
+
month: NotRequired[Union[int, str]]
|
|
369
|
+
day_of_month: NotRequired[Union[int, str]]
|
|
370
|
+
day_of_week: NotRequired[Union[int, str]]
|
|
371
|
+
comments: Optional[list[str]]
|
|
372
|
+
special_time: NotRequired[str]
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class Crontab(FactBase[Dict[CrontabCommand, CrontabDict]]):
|
|
340
376
|
"""
|
|
341
377
|
Returns a dictionary of cron command -> execution time.
|
|
342
378
|
|
|
@@ -368,7 +404,7 @@ class Crontab(FactBase):
|
|
|
368
404
|
|
|
369
405
|
@staticmethod
|
|
370
406
|
def process(output):
|
|
371
|
-
crons = {}
|
|
407
|
+
crons: dict[Command, CrontabDict] = {}
|
|
372
408
|
current_comments = []
|
|
373
409
|
|
|
374
410
|
for line in output:
|
|
@@ -478,7 +514,14 @@ class Users(FactBase):
|
|
|
478
514
|
return users
|
|
479
515
|
|
|
480
516
|
|
|
481
|
-
class
|
|
517
|
+
class LinuxDistributionDict(TypedDict):
|
|
518
|
+
name: Optional[str]
|
|
519
|
+
major: Optional[int]
|
|
520
|
+
minor: Optional[int]
|
|
521
|
+
release_meta: Dict
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class LinuxDistribution(FactBase[LinuxDistributionDict]):
|
|
482
525
|
"""
|
|
483
526
|
Returns a dict of the Linux distribution version. Ubuntu, Debian, CentOS,
|
|
484
527
|
Fedora & Gentoo currently. Also contains any key/value items located in
|
|
@@ -516,7 +559,7 @@ class LinuxDistribution(FactBase):
|
|
|
516
559
|
}
|
|
517
560
|
|
|
518
561
|
@staticmethod
|
|
519
|
-
def default():
|
|
562
|
+
def default() -> LinuxDistributionDict:
|
|
520
563
|
return {
|
|
521
564
|
"name": None,
|
|
522
565
|
"major": None,
|
|
@@ -524,7 +567,7 @@ class LinuxDistribution(FactBase):
|
|
|
524
567
|
"release_meta": {},
|
|
525
568
|
}
|
|
526
569
|
|
|
527
|
-
def process(self, output):
|
|
570
|
+
def process(self, output) -> LinuxDistributionDict:
|
|
528
571
|
parts = {}
|
|
529
572
|
for part in "\n".join(output).strip().split("---"):
|
|
530
573
|
if not part.strip():
|
|
@@ -578,7 +621,7 @@ class LinuxDistribution(FactBase):
|
|
|
578
621
|
return release_info
|
|
579
622
|
|
|
580
623
|
|
|
581
|
-
class LinuxName(ShortFactBase):
|
|
624
|
+
class LinuxName(ShortFactBase[str]):
|
|
582
625
|
"""
|
|
583
626
|
Returns the name of the Linux distribution. Shortcut for
|
|
584
627
|
``host.get_fact(LinuxDistribution)['name']``.
|
|
@@ -591,7 +634,11 @@ class LinuxName(ShortFactBase):
|
|
|
591
634
|
return data["name"]
|
|
592
635
|
|
|
593
636
|
|
|
594
|
-
class
|
|
637
|
+
class SelinuxDict(TypedDict):
|
|
638
|
+
mode: Optional[str]
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
class Selinux(FactBase[SelinuxDict]):
|
|
595
642
|
"""
|
|
596
643
|
Discovers the SELinux related facts on the target host.
|
|
597
644
|
|
|
@@ -606,12 +653,12 @@ class Selinux(FactBase):
|
|
|
606
653
|
requires_command = "sestatus"
|
|
607
654
|
|
|
608
655
|
@staticmethod
|
|
609
|
-
def default():
|
|
656
|
+
def default() -> SelinuxDict:
|
|
610
657
|
return {
|
|
611
658
|
"mode": None,
|
|
612
659
|
}
|
|
613
660
|
|
|
614
|
-
def process(self, output):
|
|
661
|
+
def process(self, output) -> SelinuxDict:
|
|
615
662
|
selinux_info = self.default()
|
|
616
663
|
|
|
617
664
|
match = re.match(r"^SELinux status:\s+(\S+)", "\n".join(output))
|
|
@@ -624,7 +671,7 @@ class Selinux(FactBase):
|
|
|
624
671
|
return selinux_info
|
|
625
672
|
|
|
626
673
|
|
|
627
|
-
class LinuxGui(FactBase):
|
|
674
|
+
class LinuxGui(FactBase[List[str]]):
|
|
628
675
|
"""
|
|
629
676
|
Returns a list of available Linux GUIs.
|
|
630
677
|
"""
|
|
@@ -640,7 +687,7 @@ class LinuxGui(FactBase):
|
|
|
640
687
|
"/usr/bin/xfce4-session": "XFCE 4",
|
|
641
688
|
}
|
|
642
689
|
|
|
643
|
-
def process(self, output):
|
|
690
|
+
def process(self, output) -> list[str]:
|
|
644
691
|
gui_names = []
|
|
645
692
|
|
|
646
693
|
for line in output:
|
|
@@ -651,7 +698,7 @@ class LinuxGui(FactBase):
|
|
|
651
698
|
return gui_names
|
|
652
699
|
|
|
653
700
|
|
|
654
|
-
class HasGui(ShortFactBase):
|
|
701
|
+
class HasGui(ShortFactBase[bool]):
|
|
655
702
|
"""
|
|
656
703
|
Returns a boolean indicating the remote side has GUI capabilities. Linux only.
|
|
657
704
|
"""
|
|
@@ -659,11 +706,11 @@ class HasGui(ShortFactBase):
|
|
|
659
706
|
fact = LinuxGui
|
|
660
707
|
|
|
661
708
|
@staticmethod
|
|
662
|
-
def process_data(data):
|
|
709
|
+
def process_data(data) -> bool:
|
|
663
710
|
return len(data) > 0
|
|
664
711
|
|
|
665
712
|
|
|
666
|
-
class Locales(FactBase):
|
|
713
|
+
class Locales(FactBase[List[str]]):
|
|
667
714
|
"""
|
|
668
715
|
Returns installed locales on the target host.
|
|
669
716
|
|
|
@@ -676,7 +723,7 @@ class Locales(FactBase):
|
|
|
676
723
|
requires_command = "locale"
|
|
677
724
|
default = list
|
|
678
725
|
|
|
679
|
-
def process(self, output):
|
|
726
|
+
def process(self, output) -> list[str]:
|
|
680
727
|
# replace utf8 with UTF-8 to match names in /etc/locale.gen
|
|
681
728
|
# return a list of enabled locales
|
|
682
729
|
return [line.replace("utf8", "UTF-8") for line in output]
|
pyinfra/facts/systemd.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
|
+
from typing import Dict, Iterable
|
|
2
3
|
|
|
3
|
-
from pyinfra.api import FactBase
|
|
4
|
+
from pyinfra.api import FactBase, FactTypeError, QuoteString, StringCommand
|
|
4
5
|
|
|
5
6
|
# Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
|
|
6
7
|
# The "unit prefix" must consist of one or more valid characters
|
|
@@ -21,21 +22,19 @@ SYSTEMD_UNIT_NAME_REGEX = (
|
|
|
21
22
|
|
|
22
23
|
def _make_systemctl_cmd(user_mode=False, machine=None, user_name=None):
|
|
23
24
|
# base command for normal and user mode
|
|
24
|
-
systemctl_cmd = "systemctl --user" if user_mode else "systemctl"
|
|
25
|
+
systemctl_cmd = ["systemctl --user"] if user_mode else ["systemctl"]
|
|
25
26
|
|
|
26
27
|
# add user and machine flag if given in args
|
|
27
28
|
if machine is not None:
|
|
28
29
|
if user_name is not None:
|
|
29
|
-
|
|
30
|
+
systemctl_cmd.append("--machine={1}@{0}".format(machine, user_name))
|
|
30
31
|
else:
|
|
31
|
-
|
|
32
|
+
systemctl_cmd.append("--machine={0}".format(machine))
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
return StringCommand(*systemctl_cmd)
|
|
34
35
|
|
|
35
|
-
return systemctl_cmd
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
class SystemdStatus(FactBase):
|
|
37
|
+
class SystemdStatus(FactBase[Dict[str, bool]]):
|
|
39
38
|
"""
|
|
40
39
|
Returns a dictionary map of systemd units to booleans indicating whether they are active.
|
|
41
40
|
|
|
@@ -58,17 +57,35 @@ class SystemdStatus(FactBase):
|
|
|
58
57
|
state_key = "SubState"
|
|
59
58
|
state_values = ["running", "waiting", "exited"]
|
|
60
59
|
|
|
61
|
-
def command(self, user_mode=False, machine=None, user_name=None):
|
|
60
|
+
def command(self, user_mode=False, machine=None, user_name=None, services=None):
|
|
62
61
|
fact_cmd = _make_systemctl_cmd(
|
|
63
62
|
user_mode=user_mode,
|
|
64
63
|
machine=machine,
|
|
65
64
|
user_name=user_name,
|
|
66
65
|
)
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
if services is None:
|
|
68
|
+
service_strs = [QuoteString("*")]
|
|
69
|
+
elif isinstance(services, str):
|
|
70
|
+
service_strs = [QuoteString(services)]
|
|
71
|
+
elif isinstance(services, Iterable):
|
|
72
|
+
service_strs = [QuoteString(s) for s in services]
|
|
73
|
+
else:
|
|
74
|
+
raise FactTypeError(f"Invalid type passed for services argument: {type(services)}")
|
|
75
|
+
|
|
76
|
+
return StringCommand(
|
|
77
|
+
fact_cmd,
|
|
78
|
+
"show",
|
|
79
|
+
"--all",
|
|
80
|
+
"--property",
|
|
81
|
+
"Id",
|
|
82
|
+
"--property",
|
|
83
|
+
self.state_key,
|
|
84
|
+
*service_strs,
|
|
85
|
+
)
|
|
69
86
|
|
|
70
|
-
def process(self, output):
|
|
71
|
-
services = {}
|
|
87
|
+
def process(self, output) -> Dict[str, bool]:
|
|
88
|
+
services: Dict[str, bool] = {}
|
|
72
89
|
|
|
73
90
|
current_unit = None
|
|
74
91
|
for line in output:
|
pyinfra/facts/sysvinit.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
4
|
+
from typing import Optional
|
|
2
5
|
|
|
3
6
|
from pyinfra.api import FactBase
|
|
4
7
|
|
|
@@ -6,7 +9,7 @@ from pyinfra.api import FactBase
|
|
|
6
9
|
class InitdStatus(FactBase):
|
|
7
10
|
"""
|
|
8
11
|
Low level check for every /etc/init.d/* script. Unfortunately many of these
|
|
9
|
-
|
|
12
|
+
misbehave and return exit status 0 while also displaying the help info/not
|
|
10
13
|
offering status support.
|
|
11
14
|
|
|
12
15
|
Returns a dict of name -> status.
|
|
@@ -29,26 +32,23 @@ class InitdStatus(FactBase):
|
|
|
29
32
|
regex = r"([a-zA-Z0-9\-]+)=([0-9]+)"
|
|
30
33
|
default = dict
|
|
31
34
|
|
|
32
|
-
def process(self, output):
|
|
33
|
-
services = {}
|
|
35
|
+
def process(self, output) -> dict[str, Optional[bool]]:
|
|
36
|
+
services: dict[str, Optional[bool]] = {}
|
|
34
37
|
|
|
35
38
|
for line in output:
|
|
36
39
|
matches = re.match(self.regex, line)
|
|
37
40
|
if matches:
|
|
38
|
-
|
|
41
|
+
intstatus = int(matches.group(2))
|
|
42
|
+
status: Optional[bool] = None
|
|
39
43
|
|
|
40
44
|
# Exit code 0 = OK/running
|
|
41
|
-
if
|
|
45
|
+
if intstatus == 0:
|
|
42
46
|
status = True
|
|
43
47
|
|
|
44
48
|
# Exit codes 1-3 = DOWN/not running
|
|
45
|
-
elif
|
|
49
|
+
elif intstatus < 4:
|
|
46
50
|
status = False
|
|
47
51
|
|
|
48
|
-
# Exit codes 4+ = unknown
|
|
49
|
-
else:
|
|
50
|
-
status = None
|
|
51
|
-
|
|
52
52
|
services[matches.group(1)] = status
|
|
53
53
|
|
|
54
54
|
return services
|
pyinfra/facts/util/packaging.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
def parse_packages(regex, output):
|
|
5
|
-
packages = {}
|
|
7
|
+
packages: dict[str, set[str]] = {}
|
|
6
8
|
|
|
7
9
|
for line in output:
|
|
8
10
|
matches = re.match(regex, line)
|
|
@@ -18,7 +20,7 @@ def parse_packages(regex, output):
|
|
|
18
20
|
def _parse_yum_or_zypper_repositories(output):
|
|
19
21
|
repos = []
|
|
20
22
|
|
|
21
|
-
current_repo = {}
|
|
23
|
+
current_repo: dict[str, str] = {}
|
|
22
24
|
for line in output:
|
|
23
25
|
line = line.strip()
|
|
24
26
|
if not line or line.startswith("#"):
|
pyinfra/local.py
CHANGED
|
@@ -6,7 +6,7 @@ import pyinfra
|
|
|
6
6
|
from pyinfra import config, host, logger, state
|
|
7
7
|
from pyinfra.api.exceptions import PyinfraError
|
|
8
8
|
from pyinfra.api.util import get_file_path
|
|
9
|
-
from pyinfra.connectors.util import run_local_process
|
|
9
|
+
from pyinfra.connectors.util import run_local_process
|
|
10
10
|
from pyinfra.context import ctx_state
|
|
11
11
|
|
|
12
12
|
|
|
@@ -76,19 +76,18 @@ def shell(
|
|
|
76
76
|
if print_input:
|
|
77
77
|
click.echo("{0}>>> {1}".format(print_prefix, command), err=True)
|
|
78
78
|
|
|
79
|
-
return_code,
|
|
79
|
+
return_code, output = run_local_process(
|
|
80
80
|
command,
|
|
81
81
|
print_output=print_output,
|
|
82
82
|
print_prefix=print_prefix,
|
|
83
83
|
)
|
|
84
|
-
stdout, stderr = split_combined_output(combined_output)
|
|
85
84
|
|
|
86
85
|
if return_code > 0 and not ignore_errors:
|
|
87
86
|
raise PyinfraError(
|
|
88
|
-
"Local command failed: {0}\n{1}".format(command, stderr),
|
|
87
|
+
"Local command failed: {0}\n{1}".format(command, output.stderr),
|
|
89
88
|
)
|
|
90
89
|
|
|
91
|
-
all_stdout.extend(
|
|
90
|
+
all_stdout.extend(output.stdout_lines)
|
|
92
91
|
|
|
93
92
|
if not splitlines:
|
|
94
93
|
return "\n".join(all_stdout)
|
pyinfra/operations/apk.py
CHANGED
|
@@ -23,7 +23,7 @@ def upgrade(available: bool = False):
|
|
|
23
23
|
yield "apk upgrade"
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
_upgrade = upgrade # noqa: E305
|
|
26
|
+
_upgrade = upgrade._inner # noqa: E305
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@operation(is_idempotent=False)
|
|
@@ -35,10 +35,10 @@ def update():
|
|
|
35
35
|
yield "apk update"
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
_update = update # noqa: E305
|
|
38
|
+
_update = update._inner # noqa: E305
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
@operation
|
|
41
|
+
@operation()
|
|
42
42
|
def packages(
|
|
43
43
|
packages=None,
|
|
44
44
|
present=True,
|
pyinfra/operations/apt.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Manage apt packages and repositories.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from datetime import
|
|
5
|
+
from datetime import timedelta
|
|
6
6
|
from urllib.parse import urlparse
|
|
7
7
|
|
|
8
|
-
from pyinfra import host
|
|
8
|
+
from pyinfra import host
|
|
9
9
|
from pyinfra.api import OperationError, operation
|
|
10
10
|
from pyinfra.facts.apt import AptKeys, AptSources, parse_apt_repo
|
|
11
11
|
from pyinfra.facts.deb import DebPackage, DebPackages
|
|
@@ -36,7 +36,7 @@ def noninteractive_apt(command, force=False):
|
|
|
36
36
|
return " ".join(args)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
@operation
|
|
39
|
+
@operation()
|
|
40
40
|
def key(src=None, keyserver=None, keyid=None):
|
|
41
41
|
"""
|
|
42
42
|
Add apt gpg keys with ``apt-key``.
|
|
@@ -78,10 +78,6 @@ def key(src=None, keyserver=None, keyid=None):
|
|
|
78
78
|
yield "(wget -O - {0} || curl -sSLf {0}) | apt-key add -".format(src)
|
|
79
79
|
else:
|
|
80
80
|
yield "apt-key add {0}".format(src)
|
|
81
|
-
|
|
82
|
-
if keyid:
|
|
83
|
-
for kid in keyid:
|
|
84
|
-
existing_keys[kid] = {}
|
|
85
81
|
else:
|
|
86
82
|
host.noop("All keys from {0} are already available in the apt keychain".format(src))
|
|
87
83
|
|
|
@@ -98,8 +94,6 @@ def key(src=None, keyserver=None, keyid=None):
|
|
|
98
94
|
keyserver,
|
|
99
95
|
" ".join(needed_keys),
|
|
100
96
|
)
|
|
101
|
-
for kid in keyid:
|
|
102
|
-
existing_keys[kid] = {}
|
|
103
97
|
else:
|
|
104
98
|
host.noop(
|
|
105
99
|
"Keys {0} are already available in the apt keychain".format(
|
|
@@ -108,7 +102,7 @@ def key(src=None, keyserver=None, keyid=None):
|
|
|
108
102
|
)
|
|
109
103
|
|
|
110
104
|
|
|
111
|
-
@operation
|
|
105
|
+
@operation()
|
|
112
106
|
def repo(src, present=True, filename=None):
|
|
113
107
|
"""
|
|
114
108
|
Add/remove apt repositories.
|
|
@@ -144,24 +138,20 @@ def repo(src, present=True, filename=None):
|
|
|
144
138
|
|
|
145
139
|
# Doesn't exist and we want it
|
|
146
140
|
if not is_present and present:
|
|
147
|
-
yield from files.line(
|
|
148
|
-
filename,
|
|
149
|
-
src,
|
|
141
|
+
yield from files.line._inner(
|
|
142
|
+
path=filename,
|
|
143
|
+
line=src,
|
|
150
144
|
escape_regex_characters=True,
|
|
151
145
|
)
|
|
152
|
-
apt_sources.append(repo)
|
|
153
146
|
|
|
154
147
|
# Exists and we don't want it
|
|
155
148
|
elif is_present and not present:
|
|
156
|
-
yield from files.line(
|
|
157
|
-
filename,
|
|
158
|
-
src,
|
|
149
|
+
yield from files.line._inner(
|
|
150
|
+
path=filename,
|
|
151
|
+
line=src,
|
|
159
152
|
present=False,
|
|
160
|
-
assume_present=True,
|
|
161
153
|
escape_regex_characters=True,
|
|
162
154
|
)
|
|
163
|
-
apt_sources.remove(repo)
|
|
164
|
-
|
|
165
155
|
else:
|
|
166
156
|
host.noop(
|
|
167
157
|
'apt repo "{0}" {1}'.format(
|
|
@@ -201,7 +191,7 @@ def ppa(src, present=True):
|
|
|
201
191
|
yield 'apt-add-repository -y --remove "{0}"'.format(src)
|
|
202
192
|
|
|
203
193
|
|
|
204
|
-
@operation
|
|
194
|
+
@operation()
|
|
205
195
|
def deb(src, present=True, force=False):
|
|
206
196
|
"""
|
|
207
197
|
Add/remove ``.deb`` file packages.
|
|
@@ -234,10 +224,10 @@ def deb(src, present=True, force=False):
|
|
|
234
224
|
# If source is a url
|
|
235
225
|
if urlparse(src).scheme:
|
|
236
226
|
# Generate a temp filename
|
|
237
|
-
temp_filename =
|
|
227
|
+
temp_filename = host.get_temp_filename(src)
|
|
238
228
|
|
|
239
229
|
# Ensure it's downloaded
|
|
240
|
-
yield from files.download(src, temp_filename)
|
|
230
|
+
yield from files.download._inner(src=src, dest=temp_filename)
|
|
241
231
|
|
|
242
232
|
# Override the source with the downloaded file
|
|
243
233
|
src = temp_filename
|
|
@@ -266,9 +256,6 @@ def deb(src, present=True, force=False):
|
|
|
266
256
|
# Now reinstall, and critically configure, the package - if there are still
|
|
267
257
|
# missing deps, now we error
|
|
268
258
|
yield "dpkg --force-confdef --force-confold -i {0}".format(src)
|
|
269
|
-
|
|
270
|
-
if info:
|
|
271
|
-
current_packages[info["name"]] = [info.get("version")]
|
|
272
259
|
else:
|
|
273
260
|
host.noop("deb {0} is installed".format(original_src))
|
|
274
261
|
|
|
@@ -279,7 +266,6 @@ def deb(src, present=True, force=False):
|
|
|
279
266
|
noninteractive_apt("remove", force=force),
|
|
280
267
|
info["name"],
|
|
281
268
|
)
|
|
282
|
-
current_packages.pop(info["name"])
|
|
283
269
|
else:
|
|
284
270
|
host.noop("deb {0} is not installed".format(original_src))
|
|
285
271
|
|
|
@@ -326,14 +312,6 @@ def update(cache_time=None):
|
|
|
326
312
|
# cache_time to work.
|
|
327
313
|
if cache_time:
|
|
328
314
|
yield "touch {0}".format(APT_UPDATE_FILENAME)
|
|
329
|
-
if cache_info is None:
|
|
330
|
-
host.create_fact(
|
|
331
|
-
File,
|
|
332
|
-
kwargs={"path": APT_UPDATE_FILENAME},
|
|
333
|
-
data={"mtime": datetime.utcnow()},
|
|
334
|
-
)
|
|
335
|
-
else:
|
|
336
|
-
cache_info["mtime"] = datetime.utcnow()
|
|
337
315
|
|
|
338
316
|
|
|
339
317
|
_update = update # noqa: E305
|
|
@@ -390,7 +368,7 @@ def dist_upgrade():
|
|
|
390
368
|
yield noninteractive_apt("dist-upgrade")
|
|
391
369
|
|
|
392
370
|
|
|
393
|
-
@operation
|
|
371
|
+
@operation()
|
|
394
372
|
def packages(
|
|
395
373
|
packages=None,
|
|
396
374
|
present=True,
|
|
@@ -454,29 +432,29 @@ def packages(
|
|
|
454
432
|
"""
|
|
455
433
|
|
|
456
434
|
if update:
|
|
457
|
-
yield from _update(cache_time=cache_time)
|
|
435
|
+
yield from _update._inner(cache_time=cache_time)
|
|
458
436
|
|
|
459
437
|
if upgrade:
|
|
460
|
-
yield from _upgrade()
|
|
438
|
+
yield from _upgrade._inner()
|
|
461
439
|
|
|
462
|
-
|
|
440
|
+
install_command_args = ["install"]
|
|
463
441
|
if no_recommends is True:
|
|
464
|
-
|
|
442
|
+
install_command_args.append("--no-install-recommends")
|
|
465
443
|
if allow_downgrades:
|
|
466
|
-
|
|
444
|
+
install_command_args.append("--allow-downgrades")
|
|
467
445
|
|
|
468
|
-
upgrade_command = " ".join(
|
|
446
|
+
upgrade_command = " ".join(install_command_args)
|
|
469
447
|
|
|
470
448
|
if extra_install_args:
|
|
471
|
-
|
|
449
|
+
install_command_args.append(extra_install_args)
|
|
472
450
|
|
|
473
|
-
install_command = " ".join(
|
|
451
|
+
install_command = " ".join(install_command_args)
|
|
474
452
|
|
|
475
|
-
|
|
453
|
+
uninstall_command_args = ["remove"]
|
|
476
454
|
if extra_uninstall_args:
|
|
477
|
-
|
|
455
|
+
uninstall_command_args.append(extra_uninstall_args)
|
|
478
456
|
|
|
479
|
-
uninstall_command = " ".join(
|
|
457
|
+
uninstall_command = " ".join(uninstall_command_args)
|
|
480
458
|
|
|
481
459
|
# Compare/ensure packages are present/not
|
|
482
460
|
yield from ensure_packages(
|