ansible  2.9.27
About: Ansible is an IT Configuration Management, Deployment \
About: Ansible (2.x) is an IT Configuration Management, Deployment & Orchestration tool.
ansible download page.
  Fossies Dox: ansible-2.9.27.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

display.py
Go to the documentation of this file.
1# (c) 2014, Michael DeHaan <michael.dehaan@gmail.com>
2#
3# This file is part of Ansible
4#
5# Ansible is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansible is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
17
18from __future__ import (absolute_import, division, print_function)
19__metaclass__ = type
20
21import errno
22import fcntl
23import getpass
24import locale
25import logging
26import os
27import random
28import subprocess
29import sys
30import textwrap
31import time
32
33from struct import unpack, pack
34from termios import TIOCGWINSZ
35
36from ansible import constants as C
37from ansible.errors import AnsibleError, AnsibleAssertionError
38from ansible.module_utils._text import to_bytes, to_text
39from ansible.module_utils.six import with_metaclass
40from ansible.utils.color import stringc
41from ansible.utils.singleton import Singleton
42from ansible.utils.unsafe_proxy import wrap_var
43
44try:
45 # Python 2
46 input = raw_input
47except NameError:
48 # Python 3, we already have raw_input
49 pass
50
51
52class FilterBlackList(logging.Filter):
53 def __init__(self, blacklist):
54 self.blacklistblacklist = [logging.Filter(name) for name in blacklist]
55
56 def filter(self, record):
57 return not any(f.filter(record) for f in self.blacklistblacklist)
58
59
60class FilterUserInjector(logging.Filter):
61 """
62 This is a filter which injects the current user as the 'user' attribute on each record. We need to add this filter
63 to all logger handlers so that 3rd party libraries won't print an exception due to user not being defined.
64 """
65
66 try:
67 username = getpass.getuser()
68 except KeyError:
69 # people like to make containers w/o actual valid passwd/shadow and use host uids
70 username = 'uid=%s' % os.getuid()
71
72 def filter(self, record):
73 record.user = FilterUserInjector.username
74 return True
75
76
77logger = None
78# TODO: make this a callback event instead
79if getattr(C, 'DEFAULT_LOG_PATH'):
80 path = C.DEFAULT_LOG_PATH
81 if path and (os.path.exists(path) and os.access(path, os.W_OK)) or os.access(os.path.dirname(path), os.W_OK):
82 # NOTE: level is kept at INFO to avoid security disclosures caused by certain libraries when using DEBUG
83 logging.basicConfig(filename=path, level=logging.INFO, # DO NOT set to logging.DEBUG
84 format='%(asctime)s p=%(process)d u=%(user)s n=%(name)s | %(message)s')
85
86 logger = logging.getLogger('ansible')
87 for handler in logging.root.handlers:
88 handler.addFilter(FilterBlackList(getattr(C, 'DEFAULT_LOG_FILTER', [])))
89 handler.addFilter(FilterUserInjector())
90 else:
91 print("[WARNING]: log file at %s is not writeable and we cannot create it, aborting\n" % path, file=sys.stderr)
92
93# map color to log levels
94color_to_log_level = {C.COLOR_ERROR: logging.ERROR,
95 C.COLOR_WARN: logging.WARNING,
96 C.COLOR_OK: logging.INFO,
97 C.COLOR_SKIP: logging.WARNING,
98 C.COLOR_UNREACHABLE: logging.ERROR,
99 C.COLOR_DEBUG: logging.DEBUG,
100 C.COLOR_CHANGED: logging.INFO,
101 C.COLOR_DEPRECATE: logging.WARNING,
102 C.COLOR_VERBOSE: logging.INFO}
103
104b_COW_PATHS = (
105 b"/usr/bin/cowsay",
106 b"/usr/games/cowsay",
107 b"/usr/local/bin/cowsay", # BSD path for cowsay
108 b"/opt/local/bin/cowsay", # MacPorts path for cowsay
109)
110
111
113
114 def __init__(self, verbosity=0):
115
116 self.columnscolumns = None
117 self.verbosityverbosity = verbosity
118
119 # list of all deprecation messages to prevent duplicate display
120 self._deprecations_deprecations = {}
121 self._warns_warns = {}
122 self._errors_errors = {}
123
124 self.b_cowsayb_cowsay = None
125 self.noncownoncow = C.ANSIBLE_COW_SELECTION
126
127 self.set_cowsay_infoset_cowsay_info()
128
129 if self.b_cowsayb_cowsay:
130 try:
131 cmd = subprocess.Popen([self.b_cowsayb_cowsay, "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
132 (out, err) = cmd.communicate()
133 self.cows_availablecows_available = set([to_text(c) for c in out.split()])
134 if C.ANSIBLE_COW_WHITELIST and any(C.ANSIBLE_COW_WHITELIST):
135 self.cows_availablecows_available = set(C.ANSIBLE_COW_WHITELIST).intersection(self.cows_availablecows_available)
136 except Exception:
137 # could not execute cowsay for some reason
138 self.b_cowsayb_cowsay = False
139
140 self._set_column_width_set_column_width()
141
143 if C.ANSIBLE_NOCOWS:
144 return
145
146 if C.ANSIBLE_COW_PATH:
147 self.b_cowsayb_cowsay = C.ANSIBLE_COW_PATH
148 else:
149 for b_cow_path in b_COW_PATHS:
150 if os.path.exists(b_cow_path):
151 self.b_cowsayb_cowsay = b_cow_path
152
153 def display(self, msg, color=None, stderr=False, screen_only=False, log_only=False, newline=True):
154 """ Display a message to the user
155
156 Note: msg *must* be a unicode string to prevent UnicodeError tracebacks.
157 """
158
159 nocolor = msg
160
161 if not log_only:
162
163 has_newline = msg.endswith(u'\n')
164 if has_newline:
165 msg2 = msg[:-1]
166 else:
167 msg2 = msg
168
169 if color:
170 msg2 = stringc(msg2, color)
171
172 if has_newline or newline:
173 msg2 = msg2 + u'\n'
174
175 msg2 = to_bytes(msg2, encoding=self._output_encoding_output_encoding(stderr=stderr))
176 if sys.version_info >= (3,):
177 # Convert back to text string on python3
178 # We first convert to a byte string so that we get rid of
179 # characters that are invalid in the user's locale
180 msg2 = to_text(msg2, self._output_encoding_output_encoding(stderr=stderr), errors='replace')
181
182 # Note: After Display() class is refactored need to update the log capture
183 # code in 'bin/ansible-connection' (and other relevant places).
184 if not stderr:
185 fileobj = sys.stdout
186 else:
187 fileobj = sys.stderr
188
189 fileobj.write(msg2)
190
191 try:
192 fileobj.flush()
193 except IOError as e:
194 # Ignore EPIPE in case fileobj has been prematurely closed, eg.
195 # when piping to "head -n1"
196 if e.errno != errno.EPIPE:
197 raise
198
199 if logger and not screen_only:
200 # We first convert to a byte string so that we get rid of
201 # color and characters that are invalid in the user's locale
202 msg2 = to_bytes(nocolor.lstrip(u'\n'))
203
204 if sys.version_info >= (3,):
205 # Convert back to text string on python3
206 msg2 = to_text(msg2, self._output_encoding_output_encoding(stderr=stderr))
207
208 lvl = logging.INFO
209 if color:
210 # set logger level based on color (not great)
211 try:
212 lvl = color_to_log_level[color]
213 except KeyError:
214 # this should not happen, but JIC
215 raise AnsibleAssertionError('Invalid color supplied to display: %s' % color)
216 # actually log
217 logger.log(lvl, msg2)
218
219 def v(self, msg, host=None):
220 return self.verboseverbose(msg, host=host, caplevel=0)
221
222 def vv(self, msg, host=None):
223 return self.verboseverbose(msg, host=host, caplevel=1)
224
225 def vvv(self, msg, host=None):
226 return self.verboseverbose(msg, host=host, caplevel=2)
227
228 def vvvv(self, msg, host=None):
229 return self.verboseverbose(msg, host=host, caplevel=3)
230
231 def vvvvv(self, msg, host=None):
232 return self.verboseverbose(msg, host=host, caplevel=4)
233
234 def vvvvvv(self, msg, host=None):
235 return self.verboseverbose(msg, host=host, caplevel=5)
236
237 def debug(self, msg, host=None):
238 if C.DEFAULT_DEBUG:
239 if host is None:
240 self.displaydisplay("%6d %0.5f: %s" % (os.getpid(), time.time(), msg), color=C.COLOR_DEBUG)
241 else:
242 self.displaydisplay("%6d %0.5f [%s]: %s" % (os.getpid(), time.time(), host, msg), color=C.COLOR_DEBUG)
243
244 def verbose(self, msg, host=None, caplevel=2):
245
246 to_stderr = C.VERBOSE_TO_STDERR
247 if self.verbosityverbosity > caplevel:
248 if host is None:
249 self.displaydisplay(msg, color=C.COLOR_VERBOSE, stderr=to_stderr)
250 else:
251 self.displaydisplay("<%s> %s" % (host, msg), color=C.COLOR_VERBOSE, stderr=to_stderr)
252
253 def deprecated(self, msg, version=None, removed=False, date=None, collection_name=None):
254 ''' used to print out a deprecation message.'''
255
256 # `date` and `collection_name` are Ansible 2.10 parameters. We accept and ignore them,
257 # to avoid modules/plugins from 2.10 conformant collections to break with new enough
258 # versions of Ansible 2.9.
259
260 if not removed and not C.DEPRECATION_WARNINGS:
261 return
262
263 if not removed:
264 if version:
265 new_msg = "[DEPRECATION WARNING]: %s. This feature will be removed in version %s." % (msg, version)
266 else:
267 new_msg = "[DEPRECATION WARNING]: %s. This feature will be removed in a future release." % (msg)
268 new_msg = new_msg + " Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.\n\n"
269 else:
270 raise AnsibleError("[DEPRECATED]: %s.\nPlease update your playbooks." % msg)
271
272 wrapped = textwrap.wrap(new_msg, self.columnscolumns, drop_whitespace=False)
273 new_msg = "\n".join(wrapped) + "\n"
274
275 if new_msg not in self._deprecations_deprecations:
276 self.displaydisplay(new_msg.strip(), color=C.COLOR_DEPRECATE, stderr=True)
277 self._deprecations_deprecations[new_msg] = 1
278
279 def warning(self, msg, formatted=False):
280
281 if not formatted:
282 new_msg = "[WARNING]: %s" % msg
283 wrapped = textwrap.wrap(new_msg, self.columnscolumns)
284 new_msg = "\n".join(wrapped) + "\n"
285 else:
286 new_msg = "\n[WARNING]: \n%s" % msg
287
288 if new_msg not in self._warns_warns:
289 self.displaydisplay(new_msg, color=C.COLOR_WARN, stderr=True)
290 self._warns_warns[new_msg] = 1
291
292 def system_warning(self, msg):
293 if C.SYSTEM_WARNINGS:
294 self.warningwarning(msg)
295
296 def banner(self, msg, color=None, cows=True):
297 '''
298 Prints a header-looking line with cowsay or stars with length depending on terminal width (3 minimum)
299 '''
300 if self.b_cowsayb_cowsay and cows:
301 try:
302 self.banner_cowsaybanner_cowsay(msg)
303 return
304 except OSError:
305 self.warningwarning("somebody cleverly deleted cowsay or something during the PB run. heh.")
306
307 msg = msg.strip()
308 star_len = self.columnscolumns - len(msg)
309 if star_len <= 3:
310 star_len = 3
311 stars = u"*" * star_len
312 self.displaydisplay(u"\n%s %s" % (msg, stars), color=color)
313
314 def banner_cowsay(self, msg, color=None):
315 if u": [" in msg:
316 msg = msg.replace(u"[", u"")
317 if msg.endswith(u"]"):
318 msg = msg[:-1]
319 runcmd = [self.b_cowsayb_cowsay, b"-W", b"60"]
320 if self.noncownoncow:
321 thecow = self.noncownoncow
322 if thecow == 'random':
323 thecow = random.choice(list(self.cows_availablecows_available))
324 runcmd.append(b'-f')
325 runcmd.append(to_bytes(thecow))
326 runcmd.append(to_bytes(msg))
327 cmd = subprocess.Popen(runcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
328 (out, err) = cmd.communicate()
329 self.displaydisplay(u"%s\n" % to_text(out), color=color)
330
331 def error(self, msg, wrap_text=True):
332 if wrap_text:
333 new_msg = u"\n[ERROR]: %s" % msg
334 wrapped = textwrap.wrap(new_msg, self.columnscolumns)
335 new_msg = u"\n".join(wrapped) + u"\n"
336 else:
337 new_msg = u"ERROR! %s" % msg
338 if new_msg not in self._errors_errors:
339 self.displaydisplay(new_msg, color=C.COLOR_ERROR, stderr=True)
340 self._errors_errors[new_msg] = 1
341
342 @staticmethod
343 def prompt(msg, private=False):
344 prompt_string = to_bytes(msg, encoding=Display._output_encoding())
345 if sys.version_info >= (3,):
346 # Convert back into text on python3. We do this double conversion
347 # to get rid of characters that are illegal in the user's locale
348 prompt_string = to_text(prompt_string)
349
350 if private:
351 return getpass.getpass(prompt_string)
352 else:
353 return input(prompt_string)
354
355 def do_var_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None, unsafe=None):
356
357 result = None
358 if sys.__stdin__.isatty():
359
360 do_prompt = self.promptprompt
361
362 if prompt and default is not None:
363 msg = "%s [%s]: " % (prompt, default)
364 elif prompt:
365 msg = "%s: " % prompt
366 else:
367 msg = 'input for %s: ' % varname
368
369 if confirm:
370 while True:
371 result = do_prompt(msg, private)
372 second = do_prompt("confirm " + msg, private)
373 if result == second:
374 break
375 self.displaydisplay("***** VALUES ENTERED DO NOT MATCH ****")
376 else:
377 result = do_prompt(msg, private)
378 else:
379 result = None
380 self.warningwarning("Not prompting as we are not in interactive mode")
381
382 # if result is false and default is not None
383 if not result and default is not None:
384 result = default
385
386 if encrypt:
387 # Circular import because encrypt needs a display class
388 from ansible.utils.encrypt import do_encrypt
389 result = do_encrypt(result, encrypt, salt_size, salt)
390
391 # handle utf-8 chars
392 result = to_text(result, errors='surrogate_or_strict')
393
394 if unsafe:
395 result = wrap_var(result)
396 return result
397
398 @staticmethod
399 def _output_encoding(stderr=False):
400 encoding = locale.getpreferredencoding()
401 # https://bugs.python.org/issue6202
402 # Python2 hardcodes an obsolete value on Mac. Use MacOSX defaults
403 # instead.
404 if encoding in ('mac-roman',):
405 encoding = 'utf-8'
406 return encoding
407
409 if os.isatty(0):
410 tty_size = unpack('HHHH', fcntl.ioctl(0, TIOCGWINSZ, pack('HHHH', 0, 0, 0, 0)))[1]
411 else:
412 tty_size = 0
413 self.columnscolumns = max(79, tty_size - 1)
def vvvv(self, msg, host=None)
Definition: display.py:228
def vvvvvv(self, msg, host=None)
Definition: display.py:234
def system_warning(self, msg)
Definition: display.py:292
def display(self, msg, color=None, stderr=False, screen_only=False, log_only=False, newline=True)
Definition: display.py:153
def vvvvv(self, msg, host=None)
Definition: display.py:231
def __init__(self, verbosity=0)
Definition: display.py:114
def do_var_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None, unsafe=None)
Definition: display.py:355
def _output_encoding(stderr=False)
Definition: display.py:399
def vv(self, msg, host=None)
Definition: display.py:222
def prompt(msg, private=False)
Definition: display.py:343
def warning(self, msg, formatted=False)
Definition: display.py:279
def vvv(self, msg, host=None)
Definition: display.py:225
def verbose(self, msg, host=None, caplevel=2)
Definition: display.py:244
def banner_cowsay(self, msg, color=None)
Definition: display.py:314
def error(self, msg, wrap_text=True)
Definition: display.py:331
def deprecated(self, msg, version=None, removed=False, date=None, collection_name=None)
Definition: display.py:253
def banner(self, msg, color=None, cows=True)
Definition: display.py:296
def v(self, msg, host=None)
Definition: display.py:219
def debug(self, msg, host=None)
Definition: display.py:237
def __init__(self, blacklist)
Definition: display.py:53
def to_bytes(obj, encoding='utf-8', errors=None, nonstring='simplerepr')
Definition: _text.py:52
def to_text(obj, encoding='utf-8', errors=None, nonstring='simplerepr')
Definition: _text.py:169
def with_metaclass(meta, *bases)
Definition: __init__.py:830
def stringc(text, color)
Definition: color.py:88
def do_encrypt(result, encrypt, salt_size=None, salt=None)
Definition: encrypt.py:196