Skip to content

Commit 7ace213

Browse files
authored
Files explicit fact imports (#613)
* Rename files fact arguments from `name` -> `path`. Previously (with "magic" facts) only positional arguments were used, the move to explicit imports switches to explicit kwargs, so this updates that to match the file operations which use `path`. * Rename `server.Which` argument `name` -> `command`. * Handle kwargs properly in `Host.{create,delete}_fact`. * Use explicit fact imports in files operations. * Implement `create_fact` & `delete_fact` in the test `FakeHost`. * Update all the operations tests using file facts for explicit imports. * Rename files fact tests to be explicit style. * Accept positional arguments on `Host.get_fact`. * Update fact examples in docs to be explicit style.
1 parent 2cc126d commit 7ace213

File tree

187 files changed

+655
-697
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

187 files changed

+655
-697
lines changed

docs/api/facts.rst

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ passed (as a ``list`` of lines) to the ``process`` handler to generate fact data
88
Importing & Using Facts
99
~~~~~~~~~~~~~~~~~~~~~~~
1010

11-
Facts are available in a global namespace as attributes on host objects: ``host.fact.X``. Custom fact classes will automatically be added to the namespace when the module containing them is imported. Note that the class name is converted to snake case, so ``MyFact`` becomes ``my_fact``, available as ``host.fact.my_fact``.
11+
Like operations, facts are imported from Python modules and executed by calling `Host.get_fact`. For example:
12+
13+
.. code:: python
14+
15+
from pyinfra import host
16+
from pyinfra.facts.server import Which
17+
18+
host.get_fact(Which, command='htop')
1219
1320
1421
Example: getting swap status
@@ -34,7 +41,7 @@ This fact could then be used like so:
3441

3542
.. code:: python
3643
37-
is_swap_enabled = host.fact.swap_enabled
44+
is_swap_enabled = host.get_fact(SwapEnabled)
3845
3946
4047
Example: getting the list of files in a directory
@@ -51,9 +58,9 @@ This fact returns a list of files found in a given directory. For this fact the
5158
Returns a list of files from a start point, recursively using find.
5259
'''
5360
54-
def command(self, name):
61+
def command(self, path):
5562
# Find files in the given location
56-
return 'find {0} -type f'.format(name)
63+
return 'find {0} -type f'.format(path)
5764
5865
def process(self, output):
5966
return output # return the list of lines (files) as-is
@@ -62,7 +69,7 @@ This fact could then be used like so:
6269

6370
.. code:: python
6471
65-
list_of_files = host.fact.find_files('/some/path')
72+
list_of_files = host.get_fact(FindFiles, path='/somewhere')
6673
6774
6875
Example: getting any output from a command
@@ -89,4 +96,4 @@ This fact could then be used like so:
8996

9097
.. code:: python
9198
92-
command_output = host.fact.raw_command_output('execute my command')
99+
command_output = host.get_fact(RawCommandOutput, command='execute this command')

docs/facts.rst

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,29 @@ Facts can be executed/tested via the command line:
88
.. code:: sh
99
1010
# Example how to get multiple facts from a server myhost.com
11-
pyinfra myhost.com fact date another_fact ...
11+
pyinfra myhost.com fact server.Date server.Hostname ...
1212
13-
If you want to see all facts:
13+
If you want to pass an argument to a fact, pass it with ``key=value``. For example:
1414

1515
.. code:: sh
1616
17-
# Show all of the facts from myhost.com
18-
pyinfra myhost.com all-facts
17+
# See if the package 'openssh-server' is installed servers myhost.com and myhost2.com
18+
pyinfra myhost.com,myhost2.com fact deb.DebPackage name=openssh-server
1919
20-
If you want to pass an argument to a fact, use `:` then the argument. For example:
20+
Multiple facts with arguments may be called like so:
2121

2222
.. code:: sh
2323
24-
# See if the package 'openssh-server' is installed servers myhost.com and myhost2.com
25-
pyinfra myhost.com,myhost2.com fact deb_package:openssh-server
24+
pyinfra @local fact files.File path=setup.py files.File path=anotherfile.txt
2625
2726
You can leverage facts as part of :doc:`a deploy <deploys>` like this:
2827

2928
.. code:: py
3029
31-
# If this is an Ubuntu server
32-
if host.fact.linux_name == 'Ubuntu':
30+
from pyinfra import host
31+
from pyinfra.facts.server import LinuxName
32+
33+
if host.get_fact(LinuxName) == 'Ubuntu':
3334
apt.packages(...)
3435
3536
**Want a new fact?** Check out :doc:`the writing facts guide <./api/facts>`.

pyinfra/api/facts.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,11 +337,11 @@ def get_host_fact(state, host, name, args=None, kwargs=None):
337337
return fact_data.get(host)
338338

339339

340-
def create_host_fact(state, host, name, data, args=None):
341-
fact_data = get_facts(state, name, args=args, ensure_hosts=(host,))
340+
def create_host_fact(state, host, name, data, args=None, kwargs=None):
341+
fact_data = get_facts(state, name, args=args, kwargs=kwargs, ensure_hosts=(host,))
342342
fact_data[host] = data
343343

344344

345-
def delete_host_fact(state, host, name, args=None):
346-
fact_data = get_facts(state, name, args=args, ensure_hosts=(host,))
345+
def delete_host_fact(state, host, name, args=None, kwargs=None):
346+
fact_data = get_facts(state, name, args=args, kwargs=kwargs, ensure_hosts=(host,))
347347
fact_data.pop(host, None)

pyinfra/api/host.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,14 @@ def noop(self, description):
134134
# Host facts
135135
#
136136

137-
def get_fact(self, name_or_cls, **kwargs):
138-
return get_host_fact(self.state, self, name_or_cls, kwargs=kwargs)
137+
def get_fact(self, name_or_cls, *args, **kwargs):
138+
return get_host_fact(self.state, self, name_or_cls, args=args, kwargs=kwargs)
139139

140-
def create_fact(self, name_or_cls, data=None, args=None):
141-
return create_host_fact(self.state, self, name_or_cls, data, args)
140+
def create_fact(self, name_or_cls, data=None, kwargs=None):
141+
return create_host_fact(self.state, self, name_or_cls, data, kwargs=kwargs)
142142

143-
def delete_fact(self, name_or_cls, args=None):
144-
return delete_host_fact(self.state, self, name_or_cls, args)
143+
def delete_fact(self, name_or_cls, kwargs=None):
144+
return delete_host_fact(self.state, self, name_or_cls, kwargs=kwargs)
145145

146146
# Connector proxy
147147
#

pyinfra/facts/files.py

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,17 @@ class Sha1File(FactBase):
139139
r'^SHA1\s+\(%s\)\s+=\s+([a-zA-Z0-9]{40})$',
140140
]
141141

142-
def command(self, name):
143-
self.name = name
142+
def command(self, path):
143+
self.path = path
144144
return make_formatted_string_command((
145145
'test -e {0} && ( '
146146
'sha1sum {0} 2> /dev/null || shasum {0} 2> /dev/null || sha1 {0} '
147147
') || true'
148-
), QuoteString(name))
148+
), QuoteString(path))
149149

150150
def process(self, output):
151151
for regex in self._regexes:
152-
regex = regex % re.escape(self.name)
152+
regex = regex % re.escape(self.path)
153153
matches = re.match(regex, output[0])
154154
if matches:
155155
return matches.group(1)
@@ -165,19 +165,19 @@ class Sha256File(FactBase):
165165
r'^SHA256\s+\(%s\)\s+=\s+([a-zA-Z0-9]{64})$',
166166
]
167167

168-
def command(self, name):
169-
self.name = name
168+
def command(self, path):
169+
self.path = path
170170
return make_formatted_string_command((
171171
'test -e {0} && ( '
172172
'sha256sum {0} 2> /dev/null '
173173
'|| shasum -a 256 {0} 2> /dev/null '
174174
'|| sha256 {0} '
175175
') || true'
176-
), QuoteString(name))
176+
), QuoteString(path))
177177

178178
def process(self, output):
179179
for regex in self._regexes:
180-
regex = regex % re.escape(self.name)
180+
regex = regex % re.escape(self.path)
181181
matches = re.match(regex, output[0])
182182
if matches:
183183
return matches.group(1)
@@ -193,16 +193,16 @@ class Md5File(FactBase):
193193
r'^SHA256\s+\(%s\)\s+=\s+([a-zA-Z0-9]{32})$',
194194
]
195195

196-
def command(self, name):
197-
self.name = name
196+
def command(self, path):
197+
self.path = path
198198
return make_formatted_string_command(
199199
'test -e {0} && ( md5sum {0} 2> /dev/null || md5 {0} ) || true',
200-
QuoteString(name),
200+
QuoteString(path),
201201
)
202202

203203
def process(self, output):
204204
for regex in self._regexes:
205-
regex = regex % re.escape(self.name)
205+
regex = regex % re.escape(self.path)
206206
matches = re.match(regex, output[0])
207207
if matches:
208208
return matches.group(1)
@@ -214,21 +214,19 @@ class FindInFile(FactBase):
214214
lines if the file exists, and ``None`` if the file does not.
215215
'''
216216

217-
def command(self, name, pattern):
218-
self.exists_name = '__pyinfra_exists_{0}'.format(name)
219-
220-
# TODO: remove special charts from __pyinfra_exists thing
217+
def command(self, path, pattern):
218+
self.exists_flag = '__pyinfra_exists_{0}'.format(path)
221219

222220
return make_formatted_string_command((
223221
'grep -e {0} {1} 2> /dev/null || '
224222
'( find {1} -type f > /dev/null && echo {2} || true )'
225-
), QuoteString(pattern), QuoteString(name), QuoteString(self.exists_name))
223+
), QuoteString(pattern), QuoteString(path), QuoteString(self.exists_flag))
226224

227225
def process(self, output):
228226
# If output is the special string: no matches, so return an empty list;
229227
# this allows us to differentiate between no matches in an existing file
230228
# or a file not existing.
231-
if output and output[0] == self.exists_name:
229+
if output and output[0] == self.exists_flag:
232230
return []
233231

234232
return output
@@ -240,10 +238,10 @@ class FindFiles(FactBase):
240238
'''
241239

242240
@staticmethod
243-
def command(name):
241+
def command(path):
244242
return make_formatted_string_command(
245243
'find {0} -type f || true',
246-
QuoteString(name),
244+
QuoteString(path),
247245
)
248246

249247
@staticmethod
@@ -257,10 +255,10 @@ class FindLinks(FindFiles):
257255
'''
258256

259257
@staticmethod
260-
def command(name):
258+
def command(path):
261259
return make_formatted_string_command(
262260
'find {0} -type l || true',
263-
QuoteString(name),
261+
QuoteString(path),
264262
)
265263

266264

@@ -270,8 +268,8 @@ class FindDirectories(FindFiles):
270268
'''
271269

272270
@staticmethod
273-
def command(name):
271+
def command(path):
274272
return make_formatted_string_command(
275273
'find {0} -type d || true',
276-
QuoteString(name),
274+
QuoteString(path),
277275
)

pyinfra/facts/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ class Which(FactBase):
110110
'''
111111

112112
@staticmethod
113-
def command(name):
114-
return 'which {0} || true'.format(name)
113+
def command(command):
114+
return 'which {0} || true'.format(command)
115115

116116

117117
class Date(FactBase):

0 commit comments

Comments
 (0)