pyinfra 3.0.dev0__py2.py3-none-any.whl → 3.0.1__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.
Files changed (148) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +115 -97
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +5 -3
  5. pyinfra/api/config.py +139 -39
  6. pyinfra/api/connectors.py +5 -2
  7. pyinfra/api/deploy.py +19 -19
  8. pyinfra/api/exceptions.py +35 -4
  9. pyinfra/api/facts.py +62 -86
  10. pyinfra/api/host.py +102 -15
  11. pyinfra/api/inventory.py +4 -0
  12. pyinfra/api/operation.py +184 -118
  13. pyinfra/api/operations.py +66 -113
  14. pyinfra/api/state.py +53 -34
  15. pyinfra/api/util.py +64 -33
  16. pyinfra/connectors/base.py +65 -20
  17. pyinfra/connectors/chroot.py +15 -13
  18. pyinfra/connectors/docker.py +62 -72
  19. pyinfra/connectors/dockerssh.py +20 -19
  20. pyinfra/connectors/local.py +32 -22
  21. pyinfra/connectors/ssh.py +162 -86
  22. pyinfra/connectors/sshuserclient/client.py +1 -1
  23. pyinfra/connectors/terraform.py +57 -39
  24. pyinfra/connectors/util.py +26 -27
  25. pyinfra/connectors/vagrant.py +27 -26
  26. pyinfra/context.py +1 -0
  27. pyinfra/facts/apk.py +7 -2
  28. pyinfra/facts/apt.py +15 -7
  29. pyinfra/facts/brew.py +28 -13
  30. pyinfra/facts/bsdinit.py +9 -6
  31. pyinfra/facts/cargo.py +6 -3
  32. pyinfra/facts/choco.py +8 -4
  33. pyinfra/facts/deb.py +21 -9
  34. pyinfra/facts/dnf.py +11 -6
  35. pyinfra/facts/docker.py +30 -5
  36. pyinfra/facts/files.py +49 -33
  37. pyinfra/facts/gem.py +7 -2
  38. pyinfra/facts/git.py +14 -21
  39. pyinfra/facts/gpg.py +4 -1
  40. pyinfra/facts/hardware.py +186 -138
  41. pyinfra/facts/launchd.py +7 -2
  42. pyinfra/facts/lxd.py +8 -2
  43. pyinfra/facts/mysql.py +19 -12
  44. pyinfra/facts/npm.py +3 -1
  45. pyinfra/facts/openrc.py +8 -2
  46. pyinfra/facts/pacman.py +13 -5
  47. pyinfra/facts/pip.py +2 -0
  48. pyinfra/facts/pkg.py +5 -1
  49. pyinfra/facts/pkgin.py +7 -2
  50. pyinfra/facts/postgres.py +170 -0
  51. pyinfra/facts/postgresql.py +5 -162
  52. pyinfra/facts/rpm.py +21 -15
  53. pyinfra/facts/runit.py +70 -0
  54. pyinfra/facts/selinux.py +12 -4
  55. pyinfra/facts/server.py +240 -82
  56. pyinfra/facts/snap.py +8 -2
  57. pyinfra/facts/systemd.py +37 -13
  58. pyinfra/facts/sysvinit.py +7 -4
  59. pyinfra/facts/upstart.py +7 -2
  60. pyinfra/facts/util/packaging.py +3 -2
  61. pyinfra/facts/vzctl.py +8 -4
  62. pyinfra/facts/xbps.py +7 -2
  63. pyinfra/facts/yum.py +10 -5
  64. pyinfra/facts/zypper.py +9 -4
  65. pyinfra/operations/apk.py +5 -3
  66. pyinfra/operations/apt.py +28 -25
  67. pyinfra/operations/brew.py +60 -29
  68. pyinfra/operations/bsdinit.py +6 -4
  69. pyinfra/operations/cargo.py +3 -1
  70. pyinfra/operations/choco.py +3 -1
  71. pyinfra/operations/dnf.py +16 -20
  72. pyinfra/operations/docker.py +339 -0
  73. pyinfra/operations/files.py +187 -168
  74. pyinfra/operations/gem.py +3 -1
  75. pyinfra/operations/git.py +23 -25
  76. pyinfra/operations/iptables.py +33 -25
  77. pyinfra/operations/launchd.py +5 -6
  78. pyinfra/operations/lxd.py +7 -4
  79. pyinfra/operations/mysql.py +59 -55
  80. pyinfra/operations/npm.py +8 -1
  81. pyinfra/operations/openrc.py +5 -3
  82. pyinfra/operations/pacman.py +6 -7
  83. pyinfra/operations/pip.py +19 -12
  84. pyinfra/operations/pkg.py +3 -1
  85. pyinfra/operations/pkgin.py +5 -3
  86. pyinfra/operations/postgres.py +349 -0
  87. pyinfra/operations/postgresql.py +18 -335
  88. pyinfra/operations/puppet.py +3 -1
  89. pyinfra/operations/python.py +8 -19
  90. pyinfra/operations/runit.py +182 -0
  91. pyinfra/operations/selinux.py +47 -29
  92. pyinfra/operations/server.py +138 -67
  93. pyinfra/operations/snap.py +3 -1
  94. pyinfra/operations/ssh.py +18 -16
  95. pyinfra/operations/systemd.py +18 -12
  96. pyinfra/operations/sysvinit.py +7 -5
  97. pyinfra/operations/upstart.py +7 -5
  98. pyinfra/operations/util/__init__.py +12 -0
  99. pyinfra/operations/util/docker.py +177 -0
  100. pyinfra/operations/util/files.py +24 -16
  101. pyinfra/operations/util/packaging.py +54 -38
  102. pyinfra/operations/util/service.py +39 -47
  103. pyinfra/operations/vzctl.py +12 -10
  104. pyinfra/operations/xbps.py +5 -3
  105. pyinfra/operations/yum.py +15 -19
  106. pyinfra/operations/zypper.py +9 -10
  107. pyinfra/version.py +5 -2
  108. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/METADATA +51 -58
  109. pyinfra-3.0.1.dist-info/RECORD +168 -0
  110. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/WHEEL +1 -1
  111. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/entry_points.txt +0 -3
  112. pyinfra_cli/__main__.py +4 -3
  113. pyinfra_cli/commands.py +3 -2
  114. pyinfra_cli/exceptions.py +75 -43
  115. pyinfra_cli/inventory.py +52 -31
  116. pyinfra_cli/log.py +10 -2
  117. pyinfra_cli/main.py +88 -65
  118. pyinfra_cli/prints.py +37 -109
  119. pyinfra_cli/util.py +15 -10
  120. tests/test_api/test_api.py +2 -0
  121. tests/test_api/test_api_arguments.py +9 -9
  122. tests/test_api/test_api_deploys.py +15 -19
  123. tests/test_api/test_api_facts.py +4 -5
  124. tests/test_api/test_api_operations.py +18 -20
  125. tests/test_api/test_api_util.py +41 -2
  126. tests/test_cli/test_cli.py +14 -50
  127. tests/test_cli/test_cli_deploy.py +10 -12
  128. tests/test_cli/test_cli_exceptions.py +50 -19
  129. tests/test_cli/test_cli_inventory.py +66 -0
  130. tests/test_cli/util.py +1 -1
  131. tests/test_connectors/test_dockerssh.py +11 -8
  132. tests/test_connectors/test_ssh.py +88 -23
  133. tests/test_connectors/test_sshuserclient.py +1 -1
  134. tests/test_connectors/test_terraform.py +11 -8
  135. tests/test_connectors/test_vagrant.py +6 -6
  136. pyinfra/connectors/ansible.py +0 -175
  137. pyinfra/connectors/mech.py +0 -189
  138. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  139. pyinfra/connectors/winrm.py +0 -312
  140. pyinfra/facts/windows.py +0 -366
  141. pyinfra/facts/windows_files.py +0 -90
  142. pyinfra/operations/windows.py +0 -59
  143. pyinfra/operations/windows_files.py +0 -538
  144. pyinfra-3.0.dev0.dist-info/RECORD +0 -170
  145. tests/test_connectors/test_ansible.py +0 -64
  146. tests/test_connectors/test_mech.py +0 -126
  147. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/LICENSE.md +0 -0
  148. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/top_level.txt +0 -0
pyinfra/facts/pacman.py CHANGED
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ import shlex
4
+
1
5
  from pyinfra.api import FactBase
2
6
 
3
7
  from .util.packaging import parse_packages
@@ -17,13 +21,14 @@ class PacmanUnpackGroup(FactBase):
17
21
  ]
18
22
  """
19
23
 
20
- requires_command = "pacman"
24
+ def requires_command(self, *args, **kwargs) -> str:
25
+ return "pacman"
21
26
 
22
27
  default = list
23
28
 
24
- def command(self, name):
29
+ def command(self, package):
25
30
  # Accept failure here (|| true) for invalid/unknown packages
26
- return 'pacman -S --print-format "%n" {0} || true'.format(name)
31
+ return 'pacman -S --print-format "%n" {0} || true'.format(shlex.quote(package))
27
32
 
28
33
  def process(self, output):
29
34
  return output
@@ -40,8 +45,11 @@ class PacmanPackages(FactBase):
40
45
  }
41
46
  """
42
47
 
43
- command = "pacman -Q"
44
- requires_command = "pacman"
48
+ def command(self) -> str:
49
+ return "pacman -Q"
50
+
51
+ def requires_command(self, *args, **kwargs) -> str:
52
+ return "pacman"
45
53
 
46
54
  default = dict
47
55
 
pyinfra/facts/pip.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util.packaging import parse_packages
pyinfra/facts/pkg.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util.packaging import parse_packages
@@ -14,9 +16,11 @@ class PkgPackages(FactBase):
14
16
  }
15
17
  """
16
18
 
17
- command = "pkg info || pkg_info || true"
18
19
  regex = r"^([a-zA-Z0-9_\-\+]+)\-([0-9a-z\.]+)"
19
20
  default = dict
20
21
 
22
+ def command(self) -> str:
23
+ return "pkg info || pkg_info || true"
24
+
21
25
  def process(self, output):
22
26
  return parse_packages(self.regex, output)
pyinfra/facts/pkgin.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util.packaging import parse_packages
@@ -16,8 +18,11 @@ class PkginPackages(FactBase):
16
18
  }
17
19
  """
18
20
 
19
- command = "pkgin list"
20
- requires_command = "pkgin"
21
+ def command(self) -> str:
22
+ return "pkgin list"
23
+
24
+ def requires_command(self) -> str:
25
+ return "pkgin"
21
26
 
22
27
  default = dict
23
28
 
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ from pyinfra.api import FactBase, MaskString, QuoteString, StringCommand
4
+ from pyinfra.api.util import try_int
5
+
6
+ from .util.databases import parse_columns_and_rows
7
+
8
+
9
+ def make_psql_command(
10
+ database: str | None = None,
11
+ user: str | None = None,
12
+ password: str | None = None,
13
+ host: str | None = None,
14
+ port: str | int | None = None,
15
+ executable="psql",
16
+ ) -> StringCommand:
17
+ target_bits: list[str] = []
18
+
19
+ if password:
20
+ target_bits.append(MaskString('PGPASSWORD="{0}"'.format(password)))
21
+
22
+ target_bits.append(executable)
23
+
24
+ if database:
25
+ target_bits.append("-d {0}".format(database))
26
+
27
+ if user:
28
+ target_bits.append("-U {0}".format(user))
29
+
30
+ if host:
31
+ target_bits.append("-h {0}".format(host))
32
+
33
+ if port:
34
+ target_bits.append("-p {0}".format(port))
35
+
36
+ return StringCommand(*target_bits)
37
+
38
+
39
+ def make_execute_psql_command(command, **psql_kwargs):
40
+ return StringCommand(
41
+ make_psql_command(**psql_kwargs),
42
+ "-Ac",
43
+ QuoteString(command), # quote this whole item as a single shell argument
44
+ )
45
+
46
+
47
+ class PostgresFactBase(FactBase):
48
+ abstract = True
49
+
50
+ psql_command: str
51
+
52
+ def requires_command(self, *args, **kwargs):
53
+ return "psql"
54
+
55
+ def command(
56
+ self,
57
+ psql_user=None,
58
+ psql_password=None,
59
+ psql_host=None,
60
+ psql_port=None,
61
+ ):
62
+ return make_execute_psql_command(
63
+ self.psql_command,
64
+ user=psql_user,
65
+ password=psql_password,
66
+ host=psql_host,
67
+ port=psql_port,
68
+ )
69
+
70
+
71
+ class PostgresRoles(PostgresFactBase):
72
+ """
73
+ Returns a dict of PostgreSQL roles and data:
74
+
75
+ .. code:: python
76
+
77
+ {
78
+ "pyinfra": {
79
+ "super": true,
80
+ "createrole": false,
81
+ "createdb": false,
82
+ ...
83
+ },
84
+ }
85
+ """
86
+
87
+ default = dict
88
+ psql_command = "SELECT * FROM pg_catalog.pg_roles"
89
+
90
+ def process(self, output):
91
+ # Remove the last line of the output (row count)
92
+ output = output[:-1]
93
+ rows = parse_columns_and_rows(
94
+ output,
95
+ "|",
96
+ # Remove the "rol" prefix on column names
97
+ remove_column_prefix="rol",
98
+ )
99
+
100
+ users = {}
101
+
102
+ for details in rows:
103
+ for key, value in list(details.items()):
104
+ if key in ("oid", "connlimit"):
105
+ details[key] = try_int(value)
106
+
107
+ if key in (
108
+ "super",
109
+ "inherit",
110
+ "createrole",
111
+ "createdb",
112
+ "canlogin",
113
+ "replication",
114
+ "bypassrls",
115
+ ):
116
+ details[key] = value == "t"
117
+
118
+ users[details.pop("name")] = details
119
+
120
+ return users
121
+
122
+
123
+ class PostgresDatabases(PostgresFactBase):
124
+ """
125
+ Returns a dict of PostgreSQL databases and metadata:
126
+
127
+ .. code:: python
128
+
129
+ {
130
+ "pyinfra_stuff": {
131
+ "encoding": "UTF8",
132
+ "collate": "en_US.UTF-8",
133
+ "ctype": "en_US.UTF-8",
134
+ ...
135
+ },
136
+ }
137
+ """
138
+
139
+ default = dict
140
+ psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), * FROM pg_catalog.pg_database"
141
+
142
+ def process(self, output):
143
+ # Remove the last line of the output (row count)
144
+ output = output[:-1]
145
+ rows = parse_columns_and_rows(
146
+ output,
147
+ "|",
148
+ # Remove the "dat" prefix on column names
149
+ remove_column_prefix="dat",
150
+ )
151
+
152
+ databases = {}
153
+
154
+ for details in rows:
155
+ details["encoding"] = details.pop("pg_encoding_to_char")
156
+
157
+ for key, value in list(details.items()):
158
+ if key.endswith("id") or key in (
159
+ "dba",
160
+ "tablespace",
161
+ "connlimit",
162
+ ):
163
+ details[key] = try_int(value)
164
+
165
+ if key in ("istemplate", "allowconn"):
166
+ details[key] = value == "t"
167
+
168
+ databases[details.pop("name")] = details
169
+
170
+ return databases
@@ -1,168 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pyinfra.api import FactBase, MaskString, QuoteString, StringCommand
4
- from pyinfra.api.util import try_int
3
+ from .postgres import PostgresDatabases, PostgresRoles
5
4
 
6
- from .util.databases import parse_columns_and_rows
7
5
 
6
+ class PostgresqlRoles(PostgresRoles):
7
+ deprecated = True
8
8
 
9
- def make_psql_command(
10
- database=None,
11
- user=None,
12
- password=None,
13
- host=None,
14
- port=None,
15
- executable="psql",
16
- ):
17
- target_bits: list[str] = []
18
9
 
19
- if password:
20
- target_bits.append(MaskString('PGPASSWORD="{0}"'.format(password)))
21
-
22
- target_bits.append(executable)
23
-
24
- if database:
25
- target_bits.append("-d {0}".format(database))
26
-
27
- if user:
28
- target_bits.append("-U {0}".format(user))
29
-
30
- if host:
31
- target_bits.append("-h {0}".format(host))
32
-
33
- if port:
34
- target_bits.append("-p {0}".format(port))
35
-
36
- return StringCommand(*target_bits)
37
-
38
-
39
- def make_execute_psql_command(command, **psql_kwargs):
40
- return StringCommand(
41
- make_psql_command(**psql_kwargs),
42
- "-Ac",
43
- QuoteString(command), # quote this whole item as a single shell argument
44
- )
45
-
46
-
47
- class PostgresqlFactBase(FactBase):
48
- abstract = True
49
-
50
- psql_command: str
51
- requires_command = "psql"
52
-
53
- def command(
54
- self,
55
- psql_user=None,
56
- psql_password=None,
57
- psql_host=None,
58
- psql_port=None,
59
- ):
60
- return make_execute_psql_command(
61
- self.psql_command,
62
- user=psql_user,
63
- password=psql_password,
64
- host=psql_host,
65
- port=psql_port,
66
- )
67
-
68
-
69
- class PostgresqlRoles(PostgresqlFactBase):
70
- """
71
- Returns a dict of PostgreSQL roles and data:
72
-
73
- .. code:: python
74
-
75
- {
76
- "pyinfra": {
77
- "super": true,
78
- "createrole": false,
79
- "createdb": false,
80
- ...
81
- },
82
- }
83
- """
84
-
85
- default = dict
86
- psql_command = "SELECT * FROM pg_catalog.pg_roles"
87
-
88
- def process(self, output):
89
- # Remove the last line of the output (row count)
90
- output = output[:-1]
91
- rows = parse_columns_and_rows(
92
- output,
93
- "|",
94
- # Remove the "rol" prefix on column names
95
- remove_column_prefix="rol",
96
- )
97
-
98
- users = {}
99
-
100
- for details in rows:
101
- for key, value in list(details.items()):
102
- if key in ("oid", "connlimit"):
103
- details[key] = try_int(value)
104
-
105
- if key in (
106
- "super",
107
- "inherit",
108
- "createrole",
109
- "createdb",
110
- "canlogin",
111
- "replication",
112
- "bypassrls",
113
- ):
114
- details[key] = value == "t"
115
-
116
- users[details.pop("name")] = details
117
-
118
- return users
119
-
120
-
121
- class PostgresqlDatabases(PostgresqlFactBase):
122
- """
123
- Returns a dict of PostgreSQL databases and metadata:
124
-
125
- .. code:: python
126
-
127
- {
128
- "pyinfra_stuff": {
129
- "encoding": "UTF8",
130
- "collate": "en_US.UTF-8",
131
- "ctype": "en_US.UTF-8",
132
- ...
133
- },
134
- }
135
- """
136
-
137
- default = dict
138
- psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), * FROM pg_catalog.pg_database"
139
-
140
- def process(self, output):
141
- # Remove the last line of the output (row count)
142
- output = output[:-1]
143
- rows = parse_columns_and_rows(
144
- output,
145
- "|",
146
- # Remove the "dat" prefix on column names
147
- remove_column_prefix="dat",
148
- )
149
-
150
- databases = {}
151
-
152
- for details in rows:
153
- details["encoding"] = details.pop("pg_encoding_to_char")
154
-
155
- for key, value in list(details.items()):
156
- if key.endswith("id") or key in (
157
- "dba",
158
- "tablespace",
159
- "connlimit",
160
- ):
161
- details[key] = try_int(value)
162
-
163
- if key in ("istemplate", "allowconn"):
164
- details[key] = value == "t"
165
-
166
- databases[details.pop("name")] = details
167
-
168
- return databases
10
+ class PostgresqlDatabases(PostgresDatabases):
11
+ deprecated = True
pyinfra/facts/rpm.py CHANGED
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
4
+ import shlex
2
5
 
3
6
  from pyinfra.api import FactBase
4
7
 
@@ -19,8 +22,11 @@ class RpmPackages(FactBase):
19
22
  }
20
23
  """
21
24
 
22
- command = 'rpm --queryformat "{0}" -qa'.format(rpm_query_format)
23
- requires_command = "rpm"
25
+ def command(self) -> str:
26
+ return "rpm --queryformat {0} -qa".format(shlex.quote(rpm_query_format))
27
+
28
+ def requires_command(self) -> str:
29
+ return "rpm"
24
30
 
25
31
  default = dict
26
32
 
@@ -40,14 +46,15 @@ class RpmPackage(FactBase):
40
46
  }
41
47
  """
42
48
 
43
- requires_command = "rpm"
49
+ def requires_command(self, package) -> str:
50
+ return "rpm"
44
51
 
45
- def command(self, name):
52
+ def command(self, package) -> str:
46
53
  return (
47
- 'rpm --queryformat "{0}" -q {1} || '
54
+ "rpm --queryformat {0} -q {1} || "
48
55
  "! test -e {1} || "
49
- 'rpm --queryformat "{0}" -qp {1} 2> /dev/null'
50
- ).format(rpm_query_format, name)
56
+ "rpm --queryformat {0} -qp {1} 2> /dev/null"
57
+ ).format(shlex.quote(rpm_query_format), shlex.quote(package))
51
58
 
52
59
  def process(self, output):
53
60
  for line in output:
@@ -66,18 +73,17 @@ class RpmPackageProvides(FactBase):
66
73
 
67
74
  default = list
68
75
 
69
- requires_command = "repoquery"
76
+ def requires_command(self, *args, **kwargs) -> str:
77
+ return "repoquery"
70
78
 
71
- @staticmethod
72
- def command(name):
79
+ def command(self, package):
73
80
  # Accept failure here (|| true) for invalid/unknown packages
74
- return 'repoquery --queryformat "{0}" --whatprovides {1} || true'.format(
75
- rpm_query_format,
76
- name,
81
+ return "repoquery --queryformat {0} --whatprovides {1} || true".format(
82
+ shlex.quote(rpm_query_format),
83
+ shlex.quote(package),
77
84
  )
78
85
 
79
- @staticmethod
80
- def process(output):
86
+ def process(self, output):
81
87
  packages = []
82
88
 
83
89
  for line in output:
pyinfra/facts/runit.py ADDED
@@ -0,0 +1,70 @@
1
+ from pyinfra.api import FactBase
2
+
3
+
4
+ class RunitStatus(FactBase):
5
+ """
6
+ Returns a dict of name -> status for runit services.
7
+
8
+ + service: optionally check only for a single service
9
+ + svdir: alternative ``SVDIR``
10
+
11
+ .. code:: python
12
+
13
+ {
14
+ 'agetty-tty1': True, # service is running
15
+ 'dhcpcd': False, # service is down
16
+ 'wpa_supplicant': None, # service is managed, but not running or down,
17
+ # possibly in a fail state
18
+ }
19
+ """
20
+
21
+ default = dict
22
+
23
+ def requires_command(self, *args, **kwargs) -> str:
24
+ return "sv"
25
+
26
+ def command(self, service=None, svdir="/var/service") -> str:
27
+ if service is None:
28
+ return (
29
+ 'export SVDIR="{0}" && '
30
+ 'cd "$SVDIR" && find * -maxdepth 0 -exec sv status {{}} + 2>/dev/null'
31
+ ).format(svdir)
32
+ else:
33
+ return 'SVDIR="{0}" sv status "{1}"'.format(svdir, service)
34
+
35
+ def process(self, output):
36
+ services = {}
37
+ for line in output:
38
+ statusstr, service, _ = line.split(sep=": ", maxsplit=2)
39
+ status = None
40
+
41
+ if statusstr == "run":
42
+ status = True
43
+ elif statusstr == "down":
44
+ status = False
45
+ # another observable state is "fail"
46
+ # report as ``None`` for now
47
+
48
+ services[service] = status
49
+
50
+ return services
51
+
52
+
53
+ class RunitManaged(FactBase):
54
+ """
55
+ Returns a set of all services managed by runit
56
+
57
+ + service: optionally check only for a single service
58
+ + svdir: alternative ``SVDIR``
59
+ """
60
+
61
+ default = set
62
+
63
+ def command(self, service=None, svdir="/var/service"):
64
+ if service is None:
65
+ return 'cd "{0}" && find -mindepth 1 -maxdepth 1 -type l -printf "%f\n"'.format(svdir)
66
+ else:
67
+ return 'cd "{0}" && test -h "{1}" && echo "{1}" || true'.format(svdir, service)
68
+
69
+ def process(self, output):
70
+ return set(output)
pyinfra/facts/selinux.py CHANGED
@@ -14,7 +14,9 @@ class SEBoolean(FactBase):
14
14
  If ``boolean`` does not exist, ``SEBoolean`` returns the empty string.
15
15
  """
16
16
 
17
- requires_command = "getsebool"
17
+ def requires_command(self, boolean) -> str:
18
+ return "getsebool"
19
+
18
20
  default = str
19
21
 
20
22
  def command(self, boolean):
@@ -60,9 +62,11 @@ class FileContextMapping(FactBase):
60
62
  Note: This fact requires root privileges.
61
63
  """
62
64
 
63
- requires_command = "semanage"
64
65
  default = dict
65
66
 
67
+ def requires_command(self, target) -> str:
68
+ return "semanage"
69
+
66
70
  def command(self, target):
67
71
  return "set -o pipefail && semanage fcontext -n -l | (grep '^{0}' || true)".format(target)
68
72
 
@@ -87,11 +91,13 @@ class SEPorts(FactBase):
87
91
  }
88
92
  """
89
93
 
90
- requires_command = "semanage"
91
94
  default = dict
92
95
  # example output: amqp_port_t tcp 15672, 5671-5672 # noqa: SC100
93
96
  _regex = re.compile(r"^([\w_]+)\s+(\w+)\s+([\w\-,\s]+)$")
94
97
 
98
+ def requires_command(self) -> str:
99
+ return "semanage"
100
+
95
101
  def command(self):
96
102
  return "semanage port -ln"
97
103
 
@@ -122,9 +128,11 @@ class SEPort(FactBase):
122
128
  Note: ``policycoreutils-dev`` must be installed for this to work.
123
129
  """
124
130
 
125
- requires_command = "sepolicy"
126
131
  default = str
127
132
 
133
+ def requires_command(self, protocol, port) -> str:
134
+ return "sepolicy"
135
+
128
136
  def command(self, protocol, port):
129
137
  return "(sepolicy network -p {0} 2>/dev/null || true) | grep {1}".format(port, protocol)
130
138