1# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013 2# Copyright (c), Toshio Kuratomi <tkuratomi@ansible.com> 2016 3# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) 4 5from __future__ import absolute_import, division, print_function 6__metaclass__ = type 7 8FILE_ATTRIBUTES = { 9 'A': 'noatime', 10 'a': 'append', 11 'c': 'compressed', 12 'C': 'nocow', 13 'd': 'nodump', 14 'D': 'dirsync', 15 'e': 'extents', 16 'E': 'encrypted', 17 'h': 'blocksize', 18 'i': 'immutable', 19 'I': 'indexed', 20 'j': 'journalled', 21 'N': 'inline', 22 's': 'zero', 23 'S': 'synchronous', 24 't': 'notail', 25 'T': 'blockroot', 26 'u': 'undelete', 27 'X': 'compressedraw', 28 'Z': 'compresseddirty', 29} 30 31# Ansible modules can be written in any language. 32# The functions available here can be used to do many common tasks, 33# to simplify development of Python modules. 34 35import __main__ 36import atexit 37import errno 38import datetime 39import grp 40import fcntl 41import locale 42import os 43import pwd 44import platform 45import re 46import select 47import shlex 48import shutil 49import signal 50import stat 51import subprocess 52import sys 53import tempfile 54import time 55import traceback 56import types 57 58from itertools import chain, repeat 59 60try: 61 import syslog 62 HAS_SYSLOG = True 63except ImportError: 64 HAS_SYSLOG = False 65 66try: 67 from systemd import journal 68 # Makes sure that systemd.journal has method sendv() 69 # Double check that journal has method sendv (some packages don't) 70 has_journal = hasattr(journal, 'sendv') 71except ImportError: 72 has_journal = False 73 74HAVE_SELINUX = False 75try: 76 import ansible.module_utils.compat.selinux as selinux 77 HAVE_SELINUX = True 78except ImportError: 79 pass 80 81# Python2 & 3 way to get NoneType 82NoneType = type(None) 83 84from ansible.module_utils.compat import selectors 85 86from ._text import to_native, to_bytes, to_text 87from ansible.module_utils.common.text.converters import ( 88 jsonify, 89 container_to_bytes as json_dict_unicode_to_bytes, 90 container_to_text as json_dict_bytes_to_unicode, 91) 92 93from ansible.module_utils.common.arg_spec import ModuleArgumentSpecValidator 94 95from ansible.module_utils.common.text.formatters import ( 96 lenient_lowercase, 97 bytes_to_human, 98 human_to_bytes, 99 SIZE_RANGES, 100) 101 102try: 103 from ansible.module_utils.common._json_compat import json 104except ImportError as e: 105 print('\n{{"msg": "Error: ansible requires the stdlib json: {0}", "failed": true}}'.format(to_native(e))) 106 sys.exit(1) 107 108 109AVAILABLE_HASH_ALGORITHMS = dict() 110try: 111 import hashlib 112 113 # python 2.7.9+ and 2.7.0+ 114 for attribute in ('available_algorithms', 'algorithms'): 115 algorithms = getattr(hashlib, attribute, None) 116 if algorithms: 117 break 118 if algorithms is None: 119 # python 2.5+ 120 algorithms = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') 121 for algorithm in algorithms: 122 AVAILABLE_HASH_ALGORITHMS[algorithm] = getattr(hashlib, algorithm) 123 124 # we may have been able to import md5 but it could still not be available 125 try: 126 hashlib.md5() 127 except ValueError: 128 AVAILABLE_HASH_ALGORITHMS.pop('md5', None) 129except Exception: 130 import sha 131 AVAILABLE_HASH_ALGORITHMS = {'sha1': sha.sha} 132 try: 133 import md5 134 AVAILABLE_HASH_ALGORITHMS['md5'] = md5.md5 135 except Exception: 136 pass 137 138from ansible.module_utils.common._collections_compat import ( 139 KeysView, 140 Mapping, MutableMapping, 141 Sequence, MutableSequence, 142 Set, MutableSet, 143) 144from ansible.module_utils.common.process import get_bin_path 145from ansible.module_utils.common.file import ( 146 _PERM_BITS as PERM_BITS, 147 _EXEC_PERM_BITS as EXEC_PERM_BITS, 148 _DEFAULT_PERM as DEFAULT_PERM, 149 is_executable, 150 format_attributes, 151 get_flags_from_attributes, 152) 153from ansible.module_utils.common.sys_info import ( 154 get_distribution, 155 get_distribution_version, 156 get_platform_subclass, 157) 158from ansible.module_utils.pycompat24 import get_exception, literal_eval 159from ansible.module_utils.common.parameters import ( 160 env_fallback, 161 remove_values, 162 sanitize_keys, 163 DEFAULT_TYPE_VALIDATORS, 164 PASS_VARS, 165 PASS_BOOLS, 166) 167 168from ansible.module_utils.errors import AnsibleFallbackNotFound, AnsibleValidationErrorMultiple, UnsupportedError 169from ansible.module_utils.six import ( 170 PY2, 171 PY3, 172 b, 173 binary_type, 174 integer_types, 175 iteritems, 176 string_types, 177 text_type, 178) 179from ansible.module_utils.six.moves import map, reduce, shlex_quote 180from ansible.module_utils.common.validation import ( 181 check_missing_parameters, 182 safe_eval, 183) 184from ansible.module_utils.common._utils import get_all_subclasses as _get_all_subclasses 185from ansible.module_utils.parsing.convert_bool import BOOLEANS, BOOLEANS_FALSE, BOOLEANS_TRUE, boolean 186from ansible.module_utils.common.warnings import ( 187 deprecate, 188 get_deprecation_messages, 189 get_warning_messages, 190 warn, 191) 192 193# Note: When getting Sequence from collections, it matches with strings. If 194# this matters, make sure to check for strings before checking for sequencetype 195SEQUENCETYPE = frozenset, KeysView, Sequence 196 197PASSWORD_MATCH = re.compile(r'^(?:.+[-_\s])?pass(?:[-_\s]?(?:word|phrase|wrd|wd)?)(?:[-_\s].+)?$', re.I) 198 199imap = map 200 201try: 202 # Python 2 203 unicode 204except NameError: 205 # Python 3 206 unicode = text_type 207 208try: 209 # Python 2 210 basestring 211except NameError: 212 # Python 3 213 basestring = string_types 214 215_literal_eval = literal_eval 216 217# End of deprecated names 218 219# Internal global holding passed in params. This is consulted in case 220# multiple AnsibleModules are created. Otherwise each AnsibleModule would 221# attempt to read from stdin. Other code should not use this directly as it 222# is an internal implementation detail 223_ANSIBLE_ARGS = None 224 225 226FILE_COMMON_ARGUMENTS = dict( 227 # These are things we want. About setting metadata (mode, ownership, permissions in general) on 228 # created files (these are used by set_fs_attributes_if_different and included in 229 # load_file_common_arguments) 230 mode=dict(type='raw'), 231 owner=dict(type='str'), 232 group=dict(type='str'), 233 seuser=dict(type='str'), 234 serole=dict(type='str'), 235 selevel=dict(type='str'), 236 setype=dict(type='str'), 237 attributes=dict(type='str', aliases=['attr']), 238 unsafe_writes=dict(type='bool', default=False, fallback=(env_fallback, ['ANSIBLE_UNSAFE_WRITES'])), # should be available to any module using atomic_move 239) 240 241PASSWD_ARG_RE = re.compile(r'^[-]{0,2}pass[-]?(word|wd)?') 242 243# Used for parsing symbolic file perms 244MODE_OPERATOR_RE = re.compile(r'[+=-]') 245USERS_RE = re.compile(r'[^ugo]') 246PERMS_RE = re.compile(r'[^rwxXstugo]') 247 248# Used for determining if the system is running a new enough python version 249# and should only restrict on our documented minimum versions 250_PY3_MIN = sys.version_info[:2] >= (3, 5) 251_PY2_MIN = (2, 6) <= sys.version_info[:2] < (3,) 252_PY_MIN = _PY3_MIN or _PY2_MIN 253if not _PY_MIN: 254 print( 255 '\n{"failed": true, ' 256 '"msg": "Ansible requires a minimum of Python2 version 2.6 or Python3 version 3.5. Current version: %s"}' % ''.join(sys.version.splitlines()) 257 ) 258 sys.exit(1) 259 260 261# 262# Deprecated functions 263# 264 265def get_platform(): 266 ''' 267 **Deprecated** Use :py:func:`platform.system` directly. 268 269 :returns: Name of the platform the module is running on in a native string 270 271 Returns a native string that labels the platform ("Linux", "Solaris", etc). Currently, this is 272 the result of calling :py:func:`platform.system`. 273 ''' 274 return platform.system() 275 276# End deprecated functions 277 278 279# 280# Compat shims 281# 282 283def load_platform_subclass(cls, *args, **kwargs): 284 """**Deprecated**: Use ansible.module_utils.common.sys_info.get_platform_subclass instead""" 285 platform_cls = get_platform_subclass(cls) 286 return super(cls, platform_cls).__new__(platform_cls) 287 288 289def get_all_subclasses(cls): 290 """**Deprecated**: Use ansible.module_utils.common._utils.get_all_subclasses instead""" 291 return list(_get_all_subclasses(cls)) 292 293 294# End compat shims 295 296 297def heuristic_log_sanitize(data, no_log_values=None): 298 ''' Remove strings that look like passwords from log messages ''' 299 # Currently filters: 300 # user:pass@foo/whatever and http://username:pass@wherever/foo 301 # This code has false positives and consumes parts of logs that are 302 # not passwds 303 304 # begin: start of a passwd containing string 305 # end: end of a passwd containing string 306 # sep: char between user and passwd 307 # prev_begin: where in the overall string to start a search for 308 # a passwd 309 # sep_search_end: where in the string to end a search for the sep 310 data = to_native(data) 311 312 output = [] 313 begin = len(data) 314 prev_begin = begin 315 sep = 1 316 while sep: 317 # Find the potential end of a passwd 318 try: 319 end = data.rindex('@', 0, begin) 320 except ValueError: 321 # No passwd in the rest of the data 322 output.insert(0, data[0:begin]) 323 break 324 325 # Search for the beginning of a passwd 326 sep = None 327 sep_search_end = end 328 while not sep: 329 # URL-style username+password 330 try: 331 begin = data.rindex('://', 0, sep_search_end) 332 except ValueError: 333 # No url style in the data, check for ssh style in the 334 # rest of the string 335 begin = 0 336 # Search for separator 337 try: 338 sep = data.index(':', begin + 3, end) 339 except ValueError: 340 # No separator; choices: 341 if begin == 0: 342 # Searched the whole string so there's no password 343 # here. Return the remaining data 344 output.insert(0, data[0:begin]) 345 break 346 # Search for a different beginning of the password field. 347 sep_search_end = begin 348 continue 349 if sep: 350 # Password was found; remove it. 351 output.insert(0, data[end:prev_begin]) 352 output.insert(0, '********') 353 output.insert(0, data[begin:sep + 1]) 354 prev_begin = begin 355 356 output = ''.join(output) 357 if no_log_values: 358 output = remove_values(output, no_log_values) 359 return output 360 361 362def _load_params(): 363 ''' read the modules parameters and store them globally. 364 365 This function may be needed for certain very dynamic custom modules which 366 want to process the parameters that are being handed the module. Since 367 this is so closely tied to the implementation of modules we cannot 368 guarantee API stability for it (it may change between versions) however we 369 will try not to break it gratuitously. It is certainly more future-proof 370 to call this function and consume its outputs than to implement the logic 371 inside it as a copy in your own code. 372 ''' 373 global _ANSIBLE_ARGS 374 if _ANSIBLE_ARGS is not None: 375 buffer = _ANSIBLE_ARGS 376 else: 377 # debug overrides to read args from file or cmdline 378 379 # Avoid tracebacks when locale is non-utf8 380 # We control the args and we pass them as utf8 381 if len(sys.argv) > 1: 382 if os.path.isfile(sys.argv[1]): 383 fd = open(sys.argv[1], 'rb') 384 buffer = fd.read() 385 fd.close() 386 else: 387 buffer = sys.argv[1] 388 if PY3: 389 buffer = buffer.encode('utf-8', errors='surrogateescape') 390 # default case, read from stdin 391 else: 392 if PY2: 393 buffer = sys.stdin.read() 394 else: 395 buffer = sys.stdin.buffer.read() 396 _ANSIBLE_ARGS = buffer 397 398 try: 399 params = json.loads(buffer.decode('utf-8')) 400 except ValueError: 401 # This helper used too early for fail_json to work. 402 print('\n{"msg": "Error: Module unable to decode valid JSON on stdin. Unable to figure out what parameters were passed", "failed": true}') 403 sys.exit(1) 404 405 if PY2: 406 params = json_dict_unicode_to_bytes(params) 407 408 try: 409 return params['ANSIBLE_MODULE_ARGS'] 410 except KeyError: 411 # This helper does not have access to fail_json so we have to print 412 # json output on our own. 413 print('\n{"msg": "Error: Module unable to locate ANSIBLE_MODULE_ARGS in json data from stdin. Unable to figure out what parameters were passed", ' 414 '"failed": true}') 415 sys.exit(1) 416 417 418def missing_required_lib(library, reason=None, url=None): 419 hostname = platform.node() 420 msg = "Failed to import the required Python library (%s) on %s's Python %s." % (library, hostname, sys.executable) 421 if reason: 422 msg += " This is required %s." % reason 423 if url: 424 msg += " See %s for more info." % url 425 426 msg += (" Please read the module documentation and install it in the appropriate location." 427 " If the required library is installed, but Ansible is using the wrong Python interpreter," 428 " please consult the documentation on ansible_python_interpreter") 429 return msg 430 431 432class AnsibleModule(object): 433 def __init__(self, argument_spec, bypass_checks=False, no_log=False, 434 mutually_exclusive=None, required_together=None, 435 required_one_of=None, add_file_common_args=False, 436 supports_check_mode=False, required_if=None, required_by=None): 437 438 ''' 439 Common code for quickly building an ansible module in Python 440 (although you can write modules with anything that can return JSON). 441 442 See :ref:`developing_modules_general` for a general introduction 443 and :ref:`developing_program_flow_modules` for more detailed explanation. 444 ''' 445 446 self._name = os.path.basename(__file__) # initialize name until we can parse from options 447 self.argument_spec = argument_spec 448 self.supports_check_mode = supports_check_mode 449 self.check_mode = False 450 self.bypass_checks = bypass_checks 451 self.no_log = no_log 452 453 self.mutually_exclusive = mutually_exclusive 454 self.required_together = required_together 455 self.required_one_of = required_one_of 456 self.required_if = required_if 457 self.required_by = required_by 458 self.cleanup_files = [] 459 self._debug = False 460 self._diff = False 461 self._socket_path = None 462 self._shell = None 463 self._syslog_facility = 'LOG_USER' 464 self._verbosity = 0 465 # May be used to set modifications to the environment for any 466 # run_command invocation 467 self.run_command_environ_update = {} 468 self._clean = {} 469 self._string_conversion_action = '' 470 471 self.aliases = {} 472 self._legal_inputs = [] 473 self._options_context = list() 474 self._tmpdir = None 475 476 if add_file_common_args: 477 for k, v in FILE_COMMON_ARGUMENTS.items(): 478 if k not in self.argument_spec: 479 self.argument_spec[k] = v 480 481 # Save parameter values that should never be logged 482 self.no_log_values = set() 483 484 # check the locale as set by the current environment, and reset to 485 # a known valid (LANG=C) if it's an invalid/unavailable locale 486 self._check_locale() 487 488 self._load_params() 489 self._set_internal_properties() 490 491 self.validator = ModuleArgumentSpecValidator(self.argument_spec, 492 self.mutually_exclusive, 493 self.required_together, 494 self.required_one_of, 495 self.required_if, 496 self.required_by, 497 ) 498 499 self.validation_result = self.validator.validate(self.params) 500 self.params.update(self.validation_result.validated_parameters) 501 self.no_log_values.update(self.validation_result._no_log_values) 502 503 try: 504 error = self.validation_result.errors[0] 505 except IndexError: 506 error = None 507 508 # Fail for validation errors, even in check mode 509 if error: 510 msg = self.validation_result.errors.msg 511 if isinstance(error, UnsupportedError): 512 msg = "Unsupported parameters for ({name}) {kind}: {msg}".format(name=self._name, kind='module', msg=msg) 513 514 self.fail_json(msg=msg) 515 516 if self.check_mode and not self.supports_check_mode: 517 self.exit_json(skipped=True, msg="remote module (%s) does not support check mode" % self._name) 518 519 # This is for backwards compatibility only. 520 self._CHECK_ARGUMENT_TYPES_DISPATCHER = DEFAULT_TYPE_VALIDATORS 521 522 if not self.no_log: 523 self._log_invocation() 524 525 # selinux state caching 526 self._selinux_enabled = None 527 self._selinux_mls_enabled = None 528 self._selinux_initial_context = None 529 530 # finally, make sure we're in a sane working dir 531 self._set_cwd() 532 533 @property 534 def tmpdir(self): 535 # if _ansible_tmpdir was not set and we have a remote_tmp, 536 # the module needs to create it and clean it up once finished. 537 # otherwise we create our own module tmp dir from the system defaults 538 if self._tmpdir is None: 539 basedir = None 540 541 if self._remote_tmp is not None: 542 basedir = os.path.expanduser(os.path.expandvars(self._remote_tmp)) 543 544 if basedir is not None and not os.path.exists(basedir): 545 try: 546 os.makedirs(basedir, mode=0o700) 547 except (OSError, IOError) as e: 548 self.warn("Unable to use %s as temporary directory, " 549 "failing back to system: %s" % (basedir, to_native(e))) 550 basedir = None 551 else: 552 self.warn("Module remote_tmp %s did not exist and was " 553 "created with a mode of 0700, this may cause" 554 " issues when running as another user. To " 555 "avoid this, create the remote_tmp dir with " 556 "the correct permissions manually" % basedir) 557 558 basefile = "ansible-moduletmp-%s-" % time.time() 559 try: 560 tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir) 561 except (OSError, IOError) as e: 562 self.fail_json( 563 msg="Failed to create remote module tmp path at dir %s " 564 "with prefix %s: %s" % (basedir, basefile, to_native(e)) 565 ) 566 if not self._keep_remote_files: 567 atexit.register(shutil.rmtree, tmpdir) 568 self._tmpdir = tmpdir 569 570 return self._tmpdir 571 572 def warn(self, warning): 573 warn(warning) 574 self.log('[WARNING] %s' % warning) 575 576 def deprecate(self, msg, version=None, date=None, collection_name=None): 577 if version is not None and date is not None: 578 raise AssertionError("implementation error -- version and date must not both be set") 579 deprecate(msg, version=version, date=date, collection_name=collection_name) 580 # For compatibility, we accept that neither version nor date is set, 581 # and treat that the same as if version would haven been set 582 if date is not None: 583 self.log('[DEPRECATION WARNING] %s %s' % (msg, date)) 584 else: 585 self.log('[DEPRECATION WARNING] %s %s' % (msg, version)) 586 587 def load_file_common_arguments(self, params, path=None): 588 ''' 589 many modules deal with files, this encapsulates common 590 options that the file module accepts such that it is directly 591 available to all modules and they can share code. 592 593 Allows to overwrite the path/dest module argument by providing path. 594 ''' 595 596 if path is None: 597 path = params.get('path', params.get('dest', None)) 598 if path is None: 599 return {} 600 else: 601 path = os.path.expanduser(os.path.expandvars(path)) 602 603 b_path = to_bytes(path, errors='surrogate_or_strict') 604 # if the path is a symlink, and we're following links, get 605 # the target of the link instead for testing 606 if params.get('follow', False) and os.path.islink(b_path): 607 b_path = os.path.realpath(b_path) 608 path = to_native(b_path) 609 610 mode = params.get('mode', None) 611 owner = params.get('owner', None) 612 group = params.get('group', None) 613 614 # selinux related options 615 seuser = params.get('seuser', None) 616 serole = params.get('serole', None) 617 setype = params.get('setype', None) 618 selevel = params.get('selevel', None) 619 secontext = [seuser, serole, setype] 620 621 if self.selinux_mls_enabled(): 622 secontext.append(selevel) 623 624 default_secontext = self.selinux_default_context(path) 625 for i in range(len(default_secontext)): 626 if i is not None and secontext[i] == '_default': 627 secontext[i] = default_secontext[i] 628 629 attributes = params.get('attributes', None) 630 return dict( 631 path=path, mode=mode, owner=owner, group=group, 632 seuser=seuser, serole=serole, setype=setype, 633 selevel=selevel, secontext=secontext, attributes=attributes, 634 ) 635 636 # Detect whether using selinux that is MLS-aware. 637 # While this means you can set the level/range with 638 # selinux.lsetfilecon(), it may or may not mean that you 639 # will get the selevel as part of the context returned 640 # by selinux.lgetfilecon(). 641 642 def selinux_mls_enabled(self): 643 if self._selinux_mls_enabled is None: 644 self._selinux_mls_enabled = HAVE_SELINUX and selinux.is_selinux_mls_enabled() == 1 645 646 return self._selinux_mls_enabled 647 648 def selinux_enabled(self): 649 if self._selinux_enabled is None: 650 self._selinux_enabled = HAVE_SELINUX and selinux.is_selinux_enabled() == 1 651 652 return self._selinux_enabled 653 654 # Determine whether we need a placeholder for selevel/mls 655 def selinux_initial_context(self): 656 if self._selinux_initial_context is None: 657 self._selinux_initial_context = [None, None, None] 658 if self.selinux_mls_enabled(): 659 self._selinux_initial_context.append(None) 660 661 return self._selinux_initial_context 662 663 # If selinux fails to find a default, return an array of None 664 def selinux_default_context(self, path, mode=0): 665 context = self.selinux_initial_context() 666 if not self.selinux_enabled(): 667 return context 668 try: 669 ret = selinux.matchpathcon(to_native(path, errors='surrogate_or_strict'), mode) 670 except OSError: 671 return context 672 if ret[0] == -1: 673 return context 674 # Limit split to 4 because the selevel, the last in the list, 675 # may contain ':' characters 676 context = ret[1].split(':', 3) 677 return context 678 679 def selinux_context(self, path): 680 context = self.selinux_initial_context() 681 if not self.selinux_enabled(): 682 return context 683 try: 684 ret = selinux.lgetfilecon_raw(to_native(path, errors='surrogate_or_strict')) 685 except OSError as e: 686 if e.errno == errno.ENOENT: 687 self.fail_json(path=path, msg='path %s does not exist' % path) 688 else: 689 self.fail_json(path=path, msg='failed to retrieve selinux context') 690 if ret[0] == -1: 691 return context 692 # Limit split to 4 because the selevel, the last in the list, 693 # may contain ':' characters 694 context = ret[1].split(':', 3) 695 return context 696 697 def user_and_group(self, path, expand=True): 698 b_path = to_bytes(path, errors='surrogate_or_strict') 699 if expand: 700 b_path = os.path.expanduser(os.path.expandvars(b_path)) 701 st = os.lstat(b_path) 702 uid = st.st_uid 703 gid = st.st_gid 704 return (uid, gid) 705 706 def find_mount_point(self, path): 707 ''' 708 Takes a path and returns it's mount point 709 710 :param path: a string type with a filesystem path 711 :returns: the path to the mount point as a text type 712 ''' 713 714 b_path = os.path.realpath(to_bytes(os.path.expanduser(os.path.expandvars(path)), errors='surrogate_or_strict')) 715 while not os.path.ismount(b_path): 716 b_path = os.path.dirname(b_path) 717 718 return to_text(b_path, errors='surrogate_or_strict') 719 720 def is_special_selinux_path(self, path): 721 """ 722 Returns a tuple containing (True, selinux_context) if the given path is on a 723 NFS or other 'special' fs mount point, otherwise the return will be (False, None). 724 """ 725 try: 726 f = open('/proc/mounts', 'r') 727 mount_data = f.readlines() 728 f.close() 729 except Exception: 730 return (False, None) 731 732 path_mount_point = self.find_mount_point(path) 733 734 for line in mount_data: 735 (device, mount_point, fstype, options, rest) = line.split(' ', 4) 736 if to_bytes(path_mount_point) == to_bytes(mount_point): 737 for fs in self._selinux_special_fs: 738 if fs in fstype: 739 special_context = self.selinux_context(path_mount_point) 740 return (True, special_context) 741 742 return (False, None) 743 744 def set_default_selinux_context(self, path, changed): 745 if not self.selinux_enabled(): 746 return changed 747 context = self.selinux_default_context(path) 748 return self.set_context_if_different(path, context, False) 749 750 def set_context_if_different(self, path, context, changed, diff=None): 751 752 if not self.selinux_enabled(): 753 return changed 754 755 if self.check_file_absent_if_check_mode(path): 756 return True 757 758 cur_context = self.selinux_context(path) 759 new_context = list(cur_context) 760 # Iterate over the current context instead of the 761 # argument context, which may have selevel. 762 763 (is_special_se, sp_context) = self.is_special_selinux_path(path) 764 if is_special_se: 765 new_context = sp_context 766 else: 767 for i in range(len(cur_context)): 768 if len(context) > i: 769 if context[i] is not None and context[i] != cur_context[i]: 770 new_context[i] = context[i] 771 elif context[i] is None: 772 new_context[i] = cur_context[i] 773 774 if cur_context != new_context: 775 if diff is not None: 776 if 'before' not in diff: 777 diff['before'] = {} 778 diff['before']['secontext'] = cur_context 779 if 'after' not in diff: 780 diff['after'] = {} 781 diff['after']['secontext'] = new_context 782 783 try: 784 if self.check_mode: 785 return True 786 rc = selinux.lsetfilecon(to_native(path), ':'.join(new_context)) 787 except OSError as e: 788 self.fail_json(path=path, msg='invalid selinux context: %s' % to_native(e), 789 new_context=new_context, cur_context=cur_context, input_was=context) 790 if rc != 0: 791 self.fail_json(path=path, msg='set selinux context failed') 792 changed = True 793 return changed 794 795 def set_owner_if_different(self, path, owner, changed, diff=None, expand=True): 796 797 if owner is None: 798 return changed 799 800 b_path = to_bytes(path, errors='surrogate_or_strict') 801 if expand: 802 b_path = os.path.expanduser(os.path.expandvars(b_path)) 803 804 if self.check_file_absent_if_check_mode(b_path): 805 return True 806 807 orig_uid, orig_gid = self.user_and_group(b_path, expand) 808 try: 809 uid = int(owner) 810 except ValueError: 811 try: 812 uid = pwd.getpwnam(owner).pw_uid 813 except KeyError: 814 path = to_text(b_path) 815 self.fail_json(path=path, msg='chown failed: failed to look up user %s' % owner) 816 817 if orig_uid != uid: 818 if diff is not None: 819 if 'before' not in diff: 820 diff['before'] = {} 821 diff['before']['owner'] = orig_uid 822 if 'after' not in diff: 823 diff['after'] = {} 824 diff['after']['owner'] = uid 825 826 if self.check_mode: 827 return True 828 try: 829 os.lchown(b_path, uid, -1) 830 except (IOError, OSError) as e: 831 path = to_text(b_path) 832 self.fail_json(path=path, msg='chown failed: %s' % (to_text(e))) 833 changed = True 834 return changed 835 836 def set_group_if_different(self, path, group, changed, diff=None, expand=True): 837 838 if group is None: 839 return changed 840 841 b_path = to_bytes(path, errors='surrogate_or_strict') 842 if expand: 843 b_path = os.path.expanduser(os.path.expandvars(b_path)) 844 845 if self.check_file_absent_if_check_mode(b_path): 846 return True 847 848 orig_uid, orig_gid = self.user_and_group(b_path, expand) 849 try: 850 gid = int(group) 851 except ValueError: 852 try: 853 gid = grp.getgrnam(group).gr_gid 854 except KeyError: 855 path = to_text(b_path) 856 self.fail_json(path=path, msg='chgrp failed: failed to look up group %s' % group) 857 858 if orig_gid != gid: 859 if diff is not None: 860 if 'before' not in diff: 861 diff['before'] = {} 862 diff['before']['group'] = orig_gid 863 if 'after' not in diff: 864 diff['after'] = {} 865 diff['after']['group'] = gid 866 867 if self.check_mode: 868 return True 869 try: 870 os.lchown(b_path, -1, gid) 871 except OSError: 872 path = to_text(b_path) 873 self.fail_json(path=path, msg='chgrp failed') 874 changed = True 875 return changed 876 877 def set_mode_if_different(self, path, mode, changed, diff=None, expand=True): 878 879 if mode is None: 880 return changed 881 882 b_path = to_bytes(path, errors='surrogate_or_strict') 883 if expand: 884 b_path = os.path.expanduser(os.path.expandvars(b_path)) 885 886 if self.check_file_absent_if_check_mode(b_path): 887 return True 888 889 path_stat = os.lstat(b_path) 890 891 if not isinstance(mode, int): 892 try: 893 mode = int(mode, 8) 894 except Exception: 895 try: 896 mode = self._symbolic_mode_to_octal(path_stat, mode) 897 except Exception as e: 898 path = to_text(b_path) 899 self.fail_json(path=path, 900 msg="mode must be in octal or symbolic form", 901 details=to_native(e)) 902 903 if mode != stat.S_IMODE(mode): 904 # prevent mode from having extra info orbeing invalid long number 905 path = to_text(b_path) 906 self.fail_json(path=path, msg="Invalid mode supplied, only permission info is allowed", details=mode) 907 908 prev_mode = stat.S_IMODE(path_stat.st_mode) 909 910 if prev_mode != mode: 911 912 if diff is not None: 913 if 'before' not in diff: 914 diff['before'] = {} 915 diff['before']['mode'] = '0%03o' % prev_mode 916 if 'after' not in diff: 917 diff['after'] = {} 918 diff['after']['mode'] = '0%03o' % mode 919 920 if self.check_mode: 921 return True 922 # FIXME: comparison against string above will cause this to be executed 923 # every time 924 try: 925 if hasattr(os, 'lchmod'): 926 os.lchmod(b_path, mode) 927 else: 928 if not os.path.islink(b_path): 929 os.chmod(b_path, mode) 930 else: 931 # Attempt to set the perms of the symlink but be 932 # careful not to change the perms of the underlying 933 # file while trying 934 underlying_stat = os.stat(b_path) 935 os.chmod(b_path, mode) 936 new_underlying_stat = os.stat(b_path) 937 if underlying_stat.st_mode != new_underlying_stat.st_mode: 938 os.chmod(b_path, stat.S_IMODE(underlying_stat.st_mode)) 939 except OSError as e: 940 if os.path.islink(b_path) and e.errno in ( 941 errno.EACCES, # can't access symlink in sticky directory (stat) 942 errno.EPERM, # can't set mode on symbolic links (chmod) 943 errno.EROFS, # can't set mode on read-only filesystem 944 ): 945 pass 946 elif e.errno in (errno.ENOENT, errno.ELOOP): # Can't set mode on broken symbolic links 947 pass 948 else: 949 raise 950 except Exception as e: 951 path = to_text(b_path) 952 self.fail_json(path=path, msg='chmod failed', details=to_native(e), 953 exception=traceback.format_exc()) 954 955 path_stat = os.lstat(b_path) 956 new_mode = stat.S_IMODE(path_stat.st_mode) 957 958 if new_mode != prev_mode: 959 changed = True 960 return changed 961 962 def set_attributes_if_different(self, path, attributes, changed, diff=None, expand=True): 963 964 if attributes is None: 965 return changed 966 967 b_path = to_bytes(path, errors='surrogate_or_strict') 968 if expand: 969 b_path = os.path.expanduser(os.path.expandvars(b_path)) 970 971 if self.check_file_absent_if_check_mode(b_path): 972 return True 973 974 existing = self.get_file_attributes(b_path, include_version=False) 975 976 attr_mod = '=' 977 if attributes.startswith(('-', '+')): 978 attr_mod = attributes[0] 979 attributes = attributes[1:] 980 981 if existing.get('attr_flags', '') != attributes or attr_mod == '-': 982 attrcmd = self.get_bin_path('chattr') 983 if attrcmd: 984 attrcmd = [attrcmd, '%s%s' % (attr_mod, attributes), b_path] 985 changed = True 986 987 if diff is not None: 988 if 'before' not in diff: 989 diff['before'] = {} 990 diff['before']['attributes'] = existing.get('attr_flags') 991 if 'after' not in diff: 992 diff['after'] = {} 993 diff['after']['attributes'] = '%s%s' % (attr_mod, attributes) 994 995 if not self.check_mode: 996 try: 997 rc, out, err = self.run_command(attrcmd) 998 if rc != 0 or err: 999 raise Exception("Error while setting attributes: %s" % (out + err)) 1000 except Exception as e: 1001 self.fail_json(path=to_text(b_path), msg='chattr failed', 1002 details=to_native(e), exception=traceback.format_exc()) 1003 return changed 1004 1005 def get_file_attributes(self, path, include_version=True): 1006 output = {} 1007 attrcmd = self.get_bin_path('lsattr', False) 1008 if attrcmd: 1009 flags = '-vd' if include_version else '-d' 1010 attrcmd = [attrcmd, flags, path] 1011 try: 1012 rc, out, err = self.run_command(attrcmd) 1013 if rc == 0: 1014 res = out.split() 1015 attr_flags_idx = 0 1016 if include_version: 1017 attr_flags_idx = 1 1018 output['version'] = res[0].strip() 1019 output['attr_flags'] = res[attr_flags_idx].replace('-', '').strip() 1020 output['attributes'] = format_attributes(output['attr_flags']) 1021 except Exception: 1022 pass 1023 return output 1024 1025 @classmethod 1026 def _symbolic_mode_to_octal(cls, path_stat, symbolic_mode): 1027 """ 1028 This enables symbolic chmod string parsing as stated in the chmod man-page 1029 1030 This includes things like: "u=rw-x+X,g=r-x+X,o=r-x+X" 1031 """ 1032 1033 new_mode = stat.S_IMODE(path_stat.st_mode) 1034 1035 # Now parse all symbolic modes 1036 for mode in symbolic_mode.split(','): 1037 # Per single mode. This always contains a '+', '-' or '=' 1038 # Split it on that 1039 permlist = MODE_OPERATOR_RE.split(mode) 1040 1041 # And find all the operators 1042 opers = MODE_OPERATOR_RE.findall(mode) 1043 1044 # The user(s) where it's all about is the first element in the 1045 # 'permlist' list. Take that and remove it from the list. 1046 # An empty user or 'a' means 'all'. 1047 users = permlist.pop(0) 1048 use_umask = (users == '') 1049 if users == 'a' or users == '': 1050 users = 'ugo' 1051 1052 # Check if there are illegal characters in the user list 1053 # They can end up in 'users' because they are not split 1054 if USERS_RE.match(users): 1055 raise ValueError("bad symbolic permission for mode: %s" % mode) 1056 1057 # Now we have two list of equal length, one contains the requested 1058 # permissions and one with the corresponding operators. 1059 for idx, perms in enumerate(permlist): 1060 # Check if there are illegal characters in the permissions 1061 if PERMS_RE.match(perms): 1062 raise ValueError("bad symbolic permission for mode: %s" % mode) 1063 1064 for user in users: 1065 mode_to_apply = cls._get_octal_mode_from_symbolic_perms(path_stat, user, perms, use_umask) 1066 new_mode = cls._apply_operation_to_mode(user, opers[idx], mode_to_apply, new_mode) 1067 1068 return new_mode 1069 1070 @staticmethod 1071 def _apply_operation_to_mode(user, operator, mode_to_apply, current_mode): 1072 if operator == '=': 1073 if user == 'u': 1074 mask = stat.S_IRWXU | stat.S_ISUID 1075 elif user == 'g': 1076 mask = stat.S_IRWXG | stat.S_ISGID 1077 elif user == 'o': 1078 mask = stat.S_IRWXO | stat.S_ISVTX 1079 1080 # mask out u, g, or o permissions from current_mode and apply new permissions 1081 inverse_mask = mask ^ PERM_BITS 1082 new_mode = (current_mode & inverse_mask) | mode_to_apply 1083 elif operator == '+': 1084 new_mode = current_mode | mode_to_apply 1085 elif operator == '-': 1086 new_mode = current_mode - (current_mode & mode_to_apply) 1087 return new_mode 1088 1089 @staticmethod 1090 def _get_octal_mode_from_symbolic_perms(path_stat, user, perms, use_umask): 1091 prev_mode = stat.S_IMODE(path_stat.st_mode) 1092 1093 is_directory = stat.S_ISDIR(path_stat.st_mode) 1094 has_x_permissions = (prev_mode & EXEC_PERM_BITS) > 0 1095 apply_X_permission = is_directory or has_x_permissions 1096 1097 # Get the umask, if the 'user' part is empty, the effect is as if (a) were 1098 # given, but bits that are set in the umask are not affected. 1099 # We also need the "reversed umask" for masking 1100 umask = os.umask(0) 1101 os.umask(umask) 1102 rev_umask = umask ^ PERM_BITS 1103 1104 # Permission bits constants documented at: 1105 # http://docs.python.org/2/library/stat.html#stat.S_ISUID 1106 if apply_X_permission: 1107 X_perms = { 1108 'u': {'X': stat.S_IXUSR}, 1109 'g': {'X': stat.S_IXGRP}, 1110 'o': {'X': stat.S_IXOTH}, 1111 } 1112 else: 1113 X_perms = { 1114 'u': {'X': 0}, 1115 'g': {'X': 0}, 1116 'o': {'X': 0}, 1117 } 1118 1119 user_perms_to_modes = { 1120 'u': { 1121 'r': rev_umask & stat.S_IRUSR if use_umask else stat.S_IRUSR, 1122 'w': rev_umask & stat.S_IWUSR if use_umask else stat.S_IWUSR, 1123 'x': rev_umask & stat.S_IXUSR if use_umask else stat.S_IXUSR, 1124 's': stat.S_ISUID, 1125 't': 0, 1126 'u': prev_mode & stat.S_IRWXU, 1127 'g': (prev_mode & stat.S_IRWXG) << 3, 1128 'o': (prev_mode & stat.S_IRWXO) << 6}, 1129 'g': { 1130 'r': rev_umask & stat.S_IRGRP if use_umask else stat.S_IRGRP, 1131 'w': rev_umask & stat.S_IWGRP if use_umask else stat.S_IWGRP, 1132 'x': rev_umask & stat.S_IXGRP if use_umask else stat.S_IXGRP, 1133 's': stat.S_ISGID, 1134 't': 0, 1135 'u': (prev_mode & stat.S_IRWXU) >> 3, 1136 'g': prev_mode & stat.S_IRWXG, 1137 'o': (prev_mode & stat.S_IRWXO) << 3}, 1138 'o': { 1139 'r': rev_umask & stat.S_IROTH if use_umask else stat.S_IROTH, 1140 'w': rev_umask & stat.S_IWOTH if use_umask else stat.S_IWOTH, 1141 'x': rev_umask & stat.S_IXOTH if use_umask else stat.S_IXOTH, 1142 's': 0, 1143 't': stat.S_ISVTX, 1144 'u': (prev_mode & stat.S_IRWXU) >> 6, 1145 'g': (prev_mode & stat.S_IRWXG) >> 3, 1146 'o': prev_mode & stat.S_IRWXO}, 1147 } 1148 1149 # Insert X_perms into user_perms_to_modes 1150 for key, value in X_perms.items(): 1151 user_perms_to_modes[key].update(value) 1152 1153 def or_reduce(mode, perm): 1154 return mode | user_perms_to_modes[user][perm] 1155 1156 return reduce(or_reduce, perms, 0) 1157 1158 def set_fs_attributes_if_different(self, file_args, changed, diff=None, expand=True): 1159 # set modes owners and context as needed 1160 changed = self.set_context_if_different( 1161 file_args['path'], file_args['secontext'], changed, diff 1162 ) 1163 changed = self.set_owner_if_different( 1164 file_args['path'], file_args['owner'], changed, diff, expand 1165 ) 1166 changed = self.set_group_if_different( 1167 file_args['path'], file_args['group'], changed, diff, expand 1168 ) 1169 changed = self.set_mode_if_different( 1170 file_args['path'], file_args['mode'], changed, diff, expand 1171 ) 1172 changed = self.set_attributes_if_different( 1173 file_args['path'], file_args['attributes'], changed, diff, expand 1174 ) 1175 return changed 1176 1177 def check_file_absent_if_check_mode(self, file_path): 1178 return self.check_mode and not os.path.exists(file_path) 1179 1180 def set_directory_attributes_if_different(self, file_args, changed, diff=None, expand=True): 1181 return self.set_fs_attributes_if_different(file_args, changed, diff, expand) 1182 1183 def set_file_attributes_if_different(self, file_args, changed, diff=None, expand=True): 1184 return self.set_fs_attributes_if_different(file_args, changed, diff, expand) 1185 1186 def add_path_info(self, kwargs): 1187 ''' 1188 for results that are files, supplement the info about the file 1189 in the return path with stats about the file path. 1190 ''' 1191 1192 path = kwargs.get('path', kwargs.get('dest', None)) 1193 if path is None: 1194 return kwargs 1195 b_path = to_bytes(path, errors='surrogate_or_strict') 1196 if os.path.exists(b_path): 1197 (uid, gid) = self.user_and_group(path) 1198 kwargs['uid'] = uid 1199 kwargs['gid'] = gid 1200 try: 1201 user = pwd.getpwuid(uid)[0] 1202 except KeyError: 1203 user = str(uid) 1204 try: 1205 group = grp.getgrgid(gid)[0] 1206 except KeyError: 1207 group = str(gid) 1208 kwargs['owner'] = user 1209 kwargs['group'] = group 1210 st = os.lstat(b_path) 1211 kwargs['mode'] = '0%03o' % stat.S_IMODE(st[stat.ST_MODE]) 1212 # secontext not yet supported 1213 if os.path.islink(b_path): 1214 kwargs['state'] = 'link' 1215 elif os.path.isdir(b_path): 1216 kwargs['state'] = 'directory' 1217 elif os.stat(b_path).st_nlink > 1: 1218 kwargs['state'] = 'hard' 1219 else: 1220 kwargs['state'] = 'file' 1221 if self.selinux_enabled(): 1222 kwargs['secontext'] = ':'.join(self.selinux_context(path)) 1223 kwargs['size'] = st[stat.ST_SIZE] 1224 return kwargs 1225 1226 def _check_locale(self): 1227 ''' 1228 Uses the locale module to test the currently set locale 1229 (per the LANG and LC_CTYPE environment settings) 1230 ''' 1231 try: 1232 # setting the locale to '' uses the default locale 1233 # as it would be returned by locale.getdefaultlocale() 1234 locale.setlocale(locale.LC_ALL, '') 1235 except locale.Error: 1236 # fallback to the 'C' locale, which may cause unicode 1237 # issues but is preferable to simply failing because 1238 # of an unknown locale 1239 locale.setlocale(locale.LC_ALL, 'C') 1240 os.environ['LANG'] = 'C' 1241 os.environ['LC_ALL'] = 'C' 1242 os.environ['LC_MESSAGES'] = 'C' 1243 except Exception as e: 1244 self.fail_json(msg="An unknown error was encountered while attempting to validate the locale: %s" % 1245 to_native(e), exception=traceback.format_exc()) 1246 1247 def _set_internal_properties(self, argument_spec=None, module_parameters=None): 1248 if argument_spec is None: 1249 argument_spec = self.argument_spec 1250 if module_parameters is None: 1251 module_parameters = self.params 1252 1253 for k in PASS_VARS: 1254 # handle setting internal properties from internal ansible vars 1255 param_key = '_ansible_%s' % k 1256 if param_key in module_parameters: 1257 if k in PASS_BOOLS: 1258 setattr(self, PASS_VARS[k][0], self.boolean(module_parameters[param_key])) 1259 else: 1260 setattr(self, PASS_VARS[k][0], module_parameters[param_key]) 1261 1262 # clean up internal top level params: 1263 if param_key in self.params: 1264 del self.params[param_key] 1265 else: 1266 # use defaults if not already set 1267 if not hasattr(self, PASS_VARS[k][0]): 1268 setattr(self, PASS_VARS[k][0], PASS_VARS[k][1]) 1269 1270 def safe_eval(self, value, locals=None, include_exceptions=False): 1271 return safe_eval(value, locals, include_exceptions) 1272 1273 def _load_params(self): 1274 ''' read the input and set the params attribute. 1275 1276 This method is for backwards compatibility. The guts of the function 1277 were moved out in 2.1 so that custom modules could read the parameters. 1278 ''' 1279 # debug overrides to read args from file or cmdline 1280 self.params = _load_params() 1281 1282 def _log_to_syslog(self, msg): 1283 if HAS_SYSLOG: 1284 try: 1285 module = 'ansible-%s' % self._name 1286 facility = getattr(syslog, self._syslog_facility, syslog.LOG_USER) 1287 syslog.openlog(str(module), 0, facility) 1288 syslog.syslog(syslog.LOG_INFO, msg) 1289 except TypeError as e: 1290 self.fail_json( 1291 msg='Failed to log to syslog (%s). To proceed anyway, ' 1292 'disable syslog logging by setting no_target_syslog ' 1293 'to True in your Ansible config.' % to_native(e), 1294 exception=traceback.format_exc(), 1295 msg_to_log=msg, 1296 ) 1297 1298 def debug(self, msg): 1299 if self._debug: 1300 self.log('[debug] %s' % msg) 1301 1302 def log(self, msg, log_args=None): 1303 1304 if not self.no_log: 1305 1306 if log_args is None: 1307 log_args = dict() 1308 1309 module = 'ansible-%s' % self._name 1310 if isinstance(module, binary_type): 1311 module = module.decode('utf-8', 'replace') 1312 1313 # 6655 - allow for accented characters 1314 if not isinstance(msg, (binary_type, text_type)): 1315 raise TypeError("msg should be a string (got %s)" % type(msg)) 1316 1317 # We want journal to always take text type 1318 # syslog takes bytes on py2, text type on py3 1319 if isinstance(msg, binary_type): 1320 journal_msg = remove_values(msg.decode('utf-8', 'replace'), self.no_log_values) 1321 else: 1322 # TODO: surrogateescape is a danger here on Py3 1323 journal_msg = remove_values(msg, self.no_log_values) 1324 1325 if PY3: 1326 syslog_msg = journal_msg 1327 else: 1328 syslog_msg = journal_msg.encode('utf-8', 'replace') 1329 1330 if has_journal: 1331 journal_args = [("MODULE", os.path.basename(__file__))] 1332 for arg in log_args: 1333 journal_args.append((arg.upper(), str(log_args[arg]))) 1334 try: 1335 if HAS_SYSLOG: 1336 # If syslog_facility specified, it needs to convert 1337 # from the facility name to the facility code, and 1338 # set it as SYSLOG_FACILITY argument of journal.send() 1339 facility = getattr(syslog, 1340 self._syslog_facility, 1341 syslog.LOG_USER) >> 3 1342 journal.send(MESSAGE=u"%s %s" % (module, journal_msg), 1343 SYSLOG_FACILITY=facility, 1344 **dict(journal_args)) 1345 else: 1346 journal.send(MESSAGE=u"%s %s" % (module, journal_msg), 1347 **dict(journal_args)) 1348 except IOError: 1349 # fall back to syslog since logging to journal failed 1350 self._log_to_syslog(syslog_msg) 1351 else: 1352 self._log_to_syslog(syslog_msg) 1353 1354 def _log_invocation(self): 1355 ''' log that ansible ran the module ''' 1356 # TODO: generalize a separate log function and make log_invocation use it 1357 # Sanitize possible password argument when logging. 1358 log_args = dict() 1359 1360 for param in self.params: 1361 canon = self.aliases.get(param, param) 1362 arg_opts = self.argument_spec.get(canon, {}) 1363 no_log = arg_opts.get('no_log', None) 1364 1365 # try to proactively capture password/passphrase fields 1366 if no_log is None and PASSWORD_MATCH.search(param): 1367 log_args[param] = 'NOT_LOGGING_PASSWORD' 1368 self.warn('Module did not set no_log for %s' % param) 1369 elif self.boolean(no_log): 1370 log_args[param] = 'NOT_LOGGING_PARAMETER' 1371 else: 1372 param_val = self.params[param] 1373 if not isinstance(param_val, (text_type, binary_type)): 1374 param_val = str(param_val) 1375 elif isinstance(param_val, text_type): 1376 param_val = param_val.encode('utf-8') 1377 log_args[param] = heuristic_log_sanitize(param_val, self.no_log_values) 1378 1379 msg = ['%s=%s' % (to_native(arg), to_native(val)) for arg, val in log_args.items()] 1380 if msg: 1381 msg = 'Invoked with %s' % ' '.join(msg) 1382 else: 1383 msg = 'Invoked' 1384 1385 self.log(msg, log_args=log_args) 1386 1387 def _set_cwd(self): 1388 try: 1389 cwd = os.getcwd() 1390 if not os.access(cwd, os.F_OK | os.R_OK): 1391 raise Exception() 1392 return cwd 1393 except Exception: 1394 # we don't have access to the cwd, probably because of sudo. 1395 # Try and move to a neutral location to prevent errors 1396 for cwd in [self.tmpdir, os.path.expandvars('$HOME'), tempfile.gettempdir()]: 1397 try: 1398 if os.access(cwd, os.F_OK | os.R_OK): 1399 os.chdir(cwd) 1400 return cwd 1401 except Exception: 1402 pass 1403 # we won't error here, as it may *not* be a problem, 1404 # and we don't want to break modules unnecessarily 1405 return None 1406 1407 def get_bin_path(self, arg, required=False, opt_dirs=None): 1408 ''' 1409 Find system executable in PATH. 1410 1411 :param arg: The executable to find. 1412 :param required: if executable is not found and required is ``True``, fail_json 1413 :param opt_dirs: optional list of directories to search in addition to ``PATH`` 1414 :returns: if found return full path; otherwise return None 1415 ''' 1416 1417 bin_path = None 1418 try: 1419 bin_path = get_bin_path(arg=arg, opt_dirs=opt_dirs) 1420 except ValueError as e: 1421 if required: 1422 self.fail_json(msg=to_text(e)) 1423 else: 1424 return bin_path 1425 1426 return bin_path 1427 1428 def boolean(self, arg): 1429 '''Convert the argument to a boolean''' 1430 if arg is None: 1431 return arg 1432 1433 try: 1434 return boolean(arg) 1435 except TypeError as e: 1436 self.fail_json(msg=to_native(e)) 1437 1438 def jsonify(self, data): 1439 try: 1440 return jsonify(data) 1441 except UnicodeError as e: 1442 self.fail_json(msg=to_text(e)) 1443 1444 def from_json(self, data): 1445 return json.loads(data) 1446 1447 def add_cleanup_file(self, path): 1448 if path not in self.cleanup_files: 1449 self.cleanup_files.append(path) 1450 1451 def do_cleanup_files(self): 1452 for path in self.cleanup_files: 1453 self.cleanup(path) 1454 1455 def _return_formatted(self, kwargs): 1456 1457 self.add_path_info(kwargs) 1458 1459 if 'invocation' not in kwargs: 1460 kwargs['invocation'] = {'module_args': self.params} 1461 1462 if 'warnings' in kwargs: 1463 if isinstance(kwargs['warnings'], list): 1464 for w in kwargs['warnings']: 1465 self.warn(w) 1466 else: 1467 self.warn(kwargs['warnings']) 1468 1469 warnings = get_warning_messages() 1470 if warnings: 1471 kwargs['warnings'] = warnings 1472 1473 if 'deprecations' in kwargs: 1474 if isinstance(kwargs['deprecations'], list): 1475 for d in kwargs['deprecations']: 1476 if isinstance(d, SEQUENCETYPE) and len(d) == 2: 1477 self.deprecate(d[0], version=d[1]) 1478 elif isinstance(d, Mapping): 1479 self.deprecate(d['msg'], version=d.get('version'), date=d.get('date'), 1480 collection_name=d.get('collection_name')) 1481 else: 1482 self.deprecate(d) # pylint: disable=ansible-deprecated-no-version 1483 else: 1484 self.deprecate(kwargs['deprecations']) # pylint: disable=ansible-deprecated-no-version 1485 1486 deprecations = get_deprecation_messages() 1487 if deprecations: 1488 kwargs['deprecations'] = deprecations 1489 1490 kwargs = remove_values(kwargs, self.no_log_values) 1491 print('\n%s' % self.jsonify(kwargs)) 1492 1493 def exit_json(self, **kwargs): 1494 ''' return from the module, without error ''' 1495 1496 self.do_cleanup_files() 1497 self._return_formatted(kwargs) 1498 sys.exit(0) 1499 1500 def fail_json(self, msg, **kwargs): 1501 ''' return from the module, with an error message ''' 1502 1503 kwargs['failed'] = True 1504 kwargs['msg'] = msg 1505 1506 # Add traceback if debug or high verbosity and it is missing 1507 # NOTE: Badly named as exception, it really always has been a traceback 1508 if 'exception' not in kwargs and sys.exc_info()[2] and (self._debug or self._verbosity >= 3): 1509 if PY2: 1510 # On Python 2 this is the last (stack frame) exception and as such may be unrelated to the failure 1511 kwargs['exception'] = 'WARNING: The below traceback may *not* be related to the actual failure.\n' +\ 1512 ''.join(traceback.format_tb(sys.exc_info()[2])) 1513 else: 1514 kwargs['exception'] = ''.join(traceback.format_tb(sys.exc_info()[2])) 1515 1516 self.do_cleanup_files() 1517 self._return_formatted(kwargs) 1518 sys.exit(1) 1519 1520 def fail_on_missing_params(self, required_params=None): 1521 if not required_params: 1522 return 1523 try: 1524 check_missing_parameters(self.params, required_params) 1525 except TypeError as e: 1526 self.fail_json(msg=to_native(e)) 1527 1528 def digest_from_file(self, filename, algorithm): 1529 ''' Return hex digest of local file for a digest_method specified by name, or None if file is not present. ''' 1530 b_filename = to_bytes(filename, errors='surrogate_or_strict') 1531 1532 if not os.path.exists(b_filename): 1533 return None 1534 if os.path.isdir(b_filename): 1535 self.fail_json(msg="attempted to take checksum of directory: %s" % filename) 1536 1537 # preserve old behaviour where the third parameter was a hash algorithm object 1538 if hasattr(algorithm, 'hexdigest'): 1539 digest_method = algorithm 1540 else: 1541 try: 1542 digest_method = AVAILABLE_HASH_ALGORITHMS[algorithm]() 1543 except KeyError: 1544 self.fail_json(msg="Could not hash file '%s' with algorithm '%s'. Available algorithms: %s" % 1545 (filename, algorithm, ', '.join(AVAILABLE_HASH_ALGORITHMS))) 1546 1547 blocksize = 64 * 1024 1548 infile = open(os.path.realpath(b_filename), 'rb') 1549 block = infile.read(blocksize) 1550 while block: 1551 digest_method.update(block) 1552 block = infile.read(blocksize) 1553 infile.close() 1554 return digest_method.hexdigest() 1555 1556 def md5(self, filename): 1557 ''' Return MD5 hex digest of local file using digest_from_file(). 1558 1559 Do not use this function unless you have no other choice for: 1560 1) Optional backwards compatibility 1561 2) Compatibility with a third party protocol 1562 1563 This function will not work on systems complying with FIPS-140-2. 1564 1565 Most uses of this function can use the module.sha1 function instead. 1566 ''' 1567 if 'md5' not in AVAILABLE_HASH_ALGORITHMS: 1568 raise ValueError('MD5 not available. Possibly running in FIPS mode') 1569 return self.digest_from_file(filename, 'md5') 1570 1571 def sha1(self, filename): 1572 ''' Return SHA1 hex digest of local file using digest_from_file(). ''' 1573 return self.digest_from_file(filename, 'sha1') 1574 1575 def sha256(self, filename): 1576 ''' Return SHA-256 hex digest of local file using digest_from_file(). ''' 1577 return self.digest_from_file(filename, 'sha256') 1578 1579 def backup_local(self, fn): 1580 '''make a date-marked backup of the specified file, return True or False on success or failure''' 1581 1582 backupdest = '' 1583 if os.path.exists(fn): 1584 # backups named basename.PID.YYYY-MM-DD@HH:MM:SS~ 1585 ext = time.strftime("%Y-%m-%d@%H:%M:%S~", time.localtime(time.time())) 1586 backupdest = '%s.%s.%s' % (fn, os.getpid(), ext) 1587 1588 try: 1589 self.preserved_copy(fn, backupdest) 1590 except (shutil.Error, IOError) as e: 1591 self.fail_json(msg='Could not make backup of %s to %s: %s' % (fn, backupdest, to_native(e))) 1592 1593 return backupdest 1594 1595 def cleanup(self, tmpfile): 1596 if os.path.exists(tmpfile): 1597 try: 1598 os.unlink(tmpfile) 1599 except OSError as e: 1600 sys.stderr.write("could not cleanup %s: %s" % (tmpfile, to_native(e))) 1601 1602 def preserved_copy(self, src, dest): 1603 """Copy a file with preserved ownership, permissions and context""" 1604 1605 # shutil.copy2(src, dst) 1606 # Similar to shutil.copy(), but metadata is copied as well - in fact, 1607 # this is just shutil.copy() followed by copystat(). This is similar 1608 # to the Unix command cp -p. 1609 # 1610 # shutil.copystat(src, dst) 1611 # Copy the permission bits, last access time, last modification time, 1612 # and flags from src to dst. The file contents, owner, and group are 1613 # unaffected. src and dst are path names given as strings. 1614 1615 shutil.copy2(src, dest) 1616 1617 # Set the context 1618 if self.selinux_enabled(): 1619 context = self.selinux_context(src) 1620 self.set_context_if_different(dest, context, False) 1621 1622 # chown it 1623 try: 1624 dest_stat = os.stat(src) 1625 tmp_stat = os.stat(dest) 1626 if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid): 1627 os.chown(dest, dest_stat.st_uid, dest_stat.st_gid) 1628 except OSError as e: 1629 if e.errno != errno.EPERM: 1630 raise 1631 1632 # Set the attributes 1633 current_attribs = self.get_file_attributes(src, include_version=False) 1634 current_attribs = current_attribs.get('attr_flags', '') 1635 self.set_attributes_if_different(dest, current_attribs, True) 1636 1637 def atomic_move(self, src, dest, unsafe_writes=False): 1638 '''atomically move src to dest, copying attributes from dest, returns true on success 1639 it uses os.rename to ensure this as it is an atomic operation, rest of the function is 1640 to work around limitations, corner cases and ensure selinux context is saved if possible''' 1641 context = None 1642 dest_stat = None 1643 b_src = to_bytes(src, errors='surrogate_or_strict') 1644 b_dest = to_bytes(dest, errors='surrogate_or_strict') 1645 if os.path.exists(b_dest): 1646 try: 1647 dest_stat = os.stat(b_dest) 1648 1649 # copy mode and ownership 1650 os.chmod(b_src, dest_stat.st_mode & PERM_BITS) 1651 os.chown(b_src, dest_stat.st_uid, dest_stat.st_gid) 1652 1653 # try to copy flags if possible 1654 if hasattr(os, 'chflags') and hasattr(dest_stat, 'st_flags'): 1655 try: 1656 os.chflags(b_src, dest_stat.st_flags) 1657 except OSError as e: 1658 for err in 'EOPNOTSUPP', 'ENOTSUP': 1659 if hasattr(errno, err) and e.errno == getattr(errno, err): 1660 break 1661 else: 1662 raise 1663 except OSError as e: 1664 if e.errno != errno.EPERM: 1665 raise 1666 if self.selinux_enabled(): 1667 context = self.selinux_context(dest) 1668 else: 1669 if self.selinux_enabled(): 1670 context = self.selinux_default_context(dest) 1671 1672 creating = not os.path.exists(b_dest) 1673 1674 try: 1675 # Optimistically try a rename, solves some corner cases and can avoid useless work, throws exception if not atomic. 1676 os.rename(b_src, b_dest) 1677 except (IOError, OSError) as e: 1678 if e.errno not in [errno.EPERM, errno.EXDEV, errno.EACCES, errno.ETXTBSY, errno.EBUSY]: 1679 # only try workarounds for errno 18 (cross device), 1 (not permitted), 13 (permission denied) 1680 # and 26 (text file busy) which happens on vagrant synced folders and other 'exotic' non posix file systems 1681 self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, to_native(e)), exception=traceback.format_exc()) 1682 else: 1683 # Use bytes here. In the shippable CI, this fails with 1684 # a UnicodeError with surrogateescape'd strings for an unknown 1685 # reason (doesn't happen in a local Ubuntu16.04 VM) 1686 b_dest_dir = os.path.dirname(b_dest) 1687 b_suffix = os.path.basename(b_dest) 1688 error_msg = None 1689 tmp_dest_name = None 1690 try: 1691 tmp_dest_fd, tmp_dest_name = tempfile.mkstemp(prefix=b'.ansible_tmp', dir=b_dest_dir, suffix=b_suffix) 1692 except (OSError, IOError) as e: 1693 error_msg = 'The destination directory (%s) is not writable by the current user. Error was: %s' % (os.path.dirname(dest), to_native(e)) 1694 except TypeError: 1695 # We expect that this is happening because python3.4.x and 1696 # below can't handle byte strings in mkstemp(). 1697 # Traceback would end in something like: 1698 # file = _os.path.join(dir, pre + name + suf) 1699 # TypeError: can't concat bytes to str 1700 error_msg = ('Failed creating tmp file for atomic move. This usually happens when using Python3 less than Python3.5. ' 1701 'Please use Python2.x or Python3.5 or greater.') 1702 finally: 1703 if error_msg: 1704 if unsafe_writes: 1705 self._unsafe_writes(b_src, b_dest) 1706 else: 1707 self.fail_json(msg=error_msg, exception=traceback.format_exc()) 1708 1709 if tmp_dest_name: 1710 b_tmp_dest_name = to_bytes(tmp_dest_name, errors='surrogate_or_strict') 1711 1712 try: 1713 try: 1714 # close tmp file handle before file operations to prevent text file busy errors on vboxfs synced folders (windows host) 1715 os.close(tmp_dest_fd) 1716 # leaves tmp file behind when sudo and not root 1717 try: 1718 shutil.move(b_src, b_tmp_dest_name) 1719 except OSError: 1720 # cleanup will happen by 'rm' of tmpdir 1721 # copy2 will preserve some metadata 1722 shutil.copy2(b_src, b_tmp_dest_name) 1723 1724 if self.selinux_enabled(): 1725 self.set_context_if_different( 1726 b_tmp_dest_name, context, False) 1727 try: 1728 tmp_stat = os.stat(b_tmp_dest_name) 1729 if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid): 1730 os.chown(b_tmp_dest_name, dest_stat.st_uid, dest_stat.st_gid) 1731 except OSError as e: 1732 if e.errno != errno.EPERM: 1733 raise 1734 try: 1735 os.rename(b_tmp_dest_name, b_dest) 1736 except (shutil.Error, OSError, IOError) as e: 1737 if unsafe_writes and e.errno == errno.EBUSY: 1738 self._unsafe_writes(b_tmp_dest_name, b_dest) 1739 else: 1740 self.fail_json(msg='Unable to make %s into to %s, failed final rename from %s: %s' % 1741 (src, dest, b_tmp_dest_name, to_native(e)), exception=traceback.format_exc()) 1742 except (shutil.Error, OSError, IOError) as e: 1743 if unsafe_writes: 1744 self._unsafe_writes(b_src, b_dest) 1745 else: 1746 self.fail_json(msg='Failed to replace file: %s to %s: %s' % (src, dest, to_native(e)), exception=traceback.format_exc()) 1747 finally: 1748 self.cleanup(b_tmp_dest_name) 1749 1750 if creating: 1751 # make sure the file has the correct permissions 1752 # based on the current value of umask 1753 umask = os.umask(0) 1754 os.umask(umask) 1755 os.chmod(b_dest, DEFAULT_PERM & ~umask) 1756 try: 1757 os.chown(b_dest, os.geteuid(), os.getegid()) 1758 except OSError: 1759 # We're okay with trying our best here. If the user is not 1760 # root (or old Unices) they won't be able to chown. 1761 pass 1762 1763 if self.selinux_enabled(): 1764 # rename might not preserve context 1765 self.set_context_if_different(dest, context, False) 1766 1767 def _unsafe_writes(self, src, dest): 1768 # sadly there are some situations where we cannot ensure atomicity, but only if 1769 # the user insists and we get the appropriate error we update the file unsafely 1770 try: 1771 out_dest = in_src = None 1772 try: 1773 out_dest = open(dest, 'wb') 1774 in_src = open(src, 'rb') 1775 shutil.copyfileobj(in_src, out_dest) 1776 finally: # assuring closed files in 2.4 compatible way 1777 if out_dest: 1778 out_dest.close() 1779 if in_src: 1780 in_src.close() 1781 except (shutil.Error, OSError, IOError) as e: 1782 self.fail_json(msg='Could not write data to file (%s) from (%s): %s' % (dest, src, to_native(e)), 1783 exception=traceback.format_exc()) 1784 1785 def _clean_args(self, args): 1786 1787 if not self._clean: 1788 # create a printable version of the command for use in reporting later, 1789 # which strips out things like passwords from the args list 1790 to_clean_args = args 1791 if PY2: 1792 if isinstance(args, text_type): 1793 to_clean_args = to_bytes(args) 1794 else: 1795 if isinstance(args, binary_type): 1796 to_clean_args = to_text(args) 1797 if isinstance(args, (text_type, binary_type)): 1798 to_clean_args = shlex.split(to_clean_args) 1799 1800 clean_args = [] 1801 is_passwd = False 1802 for arg in (to_native(a) for a in to_clean_args): 1803 if is_passwd: 1804 is_passwd = False 1805 clean_args.append('********') 1806 continue 1807 if PASSWD_ARG_RE.match(arg): 1808 sep_idx = arg.find('=') 1809 if sep_idx > -1: 1810 clean_args.append('%s=********' % arg[:sep_idx]) 1811 continue 1812 else: 1813 is_passwd = True 1814 arg = heuristic_log_sanitize(arg, self.no_log_values) 1815 clean_args.append(arg) 1816 self._clean = ' '.join(shlex_quote(arg) for arg in clean_args) 1817 1818 return self._clean 1819 1820 def _restore_signal_handlers(self): 1821 # Reset SIGPIPE to SIG_DFL, otherwise in Python2.7 it gets ignored in subprocesses. 1822 if PY2 and sys.platform != 'win32': 1823 signal.signal(signal.SIGPIPE, signal.SIG_DFL) 1824 1825 def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, 1826 use_unsafe_shell=False, prompt_regex=None, environ_update=None, umask=None, encoding='utf-8', errors='surrogate_or_strict', 1827 expand_user_and_vars=True, pass_fds=None, before_communicate_callback=None, ignore_invalid_cwd=True): 1828 ''' 1829 Execute a command, returns rc, stdout, and stderr. 1830 1831 :arg args: is the command to run 1832 * If args is a list, the command will be run with shell=False. 1833 * If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False 1834 * If args is a string and use_unsafe_shell=True it runs with shell=True. 1835 :kw check_rc: Whether to call fail_json in case of non zero RC. 1836 Default False 1837 :kw close_fds: See documentation for subprocess.Popen(). Default True 1838 :kw executable: See documentation for subprocess.Popen(). Default None 1839 :kw data: If given, information to write to the stdin of the command 1840 :kw binary_data: If False, append a newline to the data. Default False 1841 :kw path_prefix: If given, additional path to find the command in. 1842 This adds to the PATH environment variable so helper commands in 1843 the same directory can also be found 1844 :kw cwd: If given, working directory to run the command inside 1845 :kw use_unsafe_shell: See `args` parameter. Default False 1846 :kw prompt_regex: Regex string (not a compiled regex) which can be 1847 used to detect prompts in the stdout which would otherwise cause 1848 the execution to hang (especially if no input data is specified) 1849 :kw environ_update: dictionary to *update* os.environ with 1850 :kw umask: Umask to be used when running the command. Default None 1851 :kw encoding: Since we return native strings, on python3 we need to 1852 know the encoding to use to transform from bytes to text. If you 1853 want to always get bytes back, use encoding=None. The default is 1854 "utf-8". This does not affect transformation of strings given as 1855 args. 1856 :kw errors: Since we return native strings, on python3 we need to 1857 transform stdout and stderr from bytes to text. If the bytes are 1858 undecodable in the ``encoding`` specified, then use this error 1859 handler to deal with them. The default is ``surrogate_or_strict`` 1860 which means that the bytes will be decoded using the 1861 surrogateescape error handler if available (available on all 1862 python3 versions we support) otherwise a UnicodeError traceback 1863 will be raised. This does not affect transformations of strings 1864 given as args. 1865 :kw expand_user_and_vars: When ``use_unsafe_shell=False`` this argument 1866 dictates whether ``~`` is expanded in paths and environment variables 1867 are expanded before running the command. When ``True`` a string such as 1868 ``$SHELL`` will be expanded regardless of escaping. When ``False`` and 1869 ``use_unsafe_shell=False`` no path or variable expansion will be done. 1870 :kw pass_fds: When running on Python 3 this argument 1871 dictates which file descriptors should be passed 1872 to an underlying ``Popen`` constructor. On Python 2, this will 1873 set ``close_fds`` to False. 1874 :kw before_communicate_callback: This function will be called 1875 after ``Popen`` object will be created 1876 but before communicating to the process. 1877 (``Popen`` object will be passed to callback as a first argument) 1878 :kw ignore_invalid_cwd: This flag indicates whether an invalid ``cwd`` 1879 (non-existent or not a directory) should be ignored or should raise 1880 an exception. 1881 :returns: A 3-tuple of return code (integer), stdout (native string), 1882 and stderr (native string). On python2, stdout and stderr are both 1883 byte strings. On python3, stdout and stderr are text strings converted 1884 according to the encoding and errors parameters. If you want byte 1885 strings on python3, use encoding=None to turn decoding to text off. 1886 ''' 1887 # used by clean args later on 1888 self._clean = None 1889 1890 if not isinstance(args, (list, binary_type, text_type)): 1891 msg = "Argument 'args' to run_command must be list or string" 1892 self.fail_json(rc=257, cmd=args, msg=msg) 1893 1894 shell = False 1895 if use_unsafe_shell: 1896 1897 # stringify args for unsafe/direct shell usage 1898 if isinstance(args, list): 1899 args = b" ".join([to_bytes(shlex_quote(x), errors='surrogate_or_strict') for x in args]) 1900 else: 1901 args = to_bytes(args, errors='surrogate_or_strict') 1902 1903 # not set explicitly, check if set by controller 1904 if executable: 1905 executable = to_bytes(executable, errors='surrogate_or_strict') 1906 args = [executable, b'-c', args] 1907 elif self._shell not in (None, '/bin/sh'): 1908 args = [to_bytes(self._shell, errors='surrogate_or_strict'), b'-c', args] 1909 else: 1910 shell = True 1911 else: 1912 # ensure args are a list 1913 if isinstance(args, (binary_type, text_type)): 1914 # On python2.6 and below, shlex has problems with text type 1915 # On python3, shlex needs a text type. 1916 if PY2: 1917 args = to_bytes(args, errors='surrogate_or_strict') 1918 elif PY3: 1919 args = to_text(args, errors='surrogateescape') 1920 args = shlex.split(args) 1921 1922 # expand ``~`` in paths, and all environment vars 1923 if expand_user_and_vars: 1924 args = [to_bytes(os.path.expanduser(os.path.expandvars(x)), errors='surrogate_or_strict') for x in args if x is not None] 1925 else: 1926 args = [to_bytes(x, errors='surrogate_or_strict') for x in args if x is not None] 1927 1928 prompt_re = None 1929 if prompt_regex: 1930 if isinstance(prompt_regex, text_type): 1931 if PY3: 1932 prompt_regex = to_bytes(prompt_regex, errors='surrogateescape') 1933 elif PY2: 1934 prompt_regex = to_bytes(prompt_regex, errors='surrogate_or_strict') 1935 try: 1936 prompt_re = re.compile(prompt_regex, re.MULTILINE) 1937 except re.error: 1938 self.fail_json(msg="invalid prompt regular expression given to run_command") 1939 1940 rc = 0 1941 msg = None 1942 st_in = None 1943 1944 # Manipulate the environ we'll send to the new process 1945 old_env_vals = {} 1946 # We can set this from both an attribute and per call 1947 for key, val in self.run_command_environ_update.items(): 1948 old_env_vals[key] = os.environ.get(key, None) 1949 os.environ[key] = val 1950 if environ_update: 1951 for key, val in environ_update.items(): 1952 old_env_vals[key] = os.environ.get(key, None) 1953 os.environ[key] = val 1954 if path_prefix: 1955 path = os.environ.get('PATH', '') 1956 old_env_vals['PATH'] = path 1957 if path: 1958 os.environ['PATH'] = "%s:%s" % (path_prefix, path) 1959 else: 1960 os.environ['PATH'] = path_prefix 1961 1962 # If using test-module.py and explode, the remote lib path will resemble: 1963 # /tmp/test_module_scratch/debug_dir/ansible/module_utils/basic.py 1964 # If using ansible or ansible-playbook with a remote system: 1965 # /tmp/ansible_vmweLQ/ansible_modlib.zip/ansible/module_utils/basic.py 1966 1967 # Clean out python paths set by ansiballz 1968 if 'PYTHONPATH' in os.environ: 1969 pypaths = os.environ['PYTHONPATH'].split(':') 1970 pypaths = [x for x in pypaths 1971 if not x.endswith('/ansible_modlib.zip') and 1972 not x.endswith('/debug_dir')] 1973 os.environ['PYTHONPATH'] = ':'.join(pypaths) 1974 if not os.environ['PYTHONPATH']: 1975 del os.environ['PYTHONPATH'] 1976 1977 if data: 1978 st_in = subprocess.PIPE 1979 1980 kwargs = dict( 1981 executable=executable, 1982 shell=shell, 1983 close_fds=close_fds, 1984 stdin=st_in, 1985 stdout=subprocess.PIPE, 1986 stderr=subprocess.PIPE, 1987 preexec_fn=self._restore_signal_handlers, 1988 ) 1989 if PY3 and pass_fds: 1990 kwargs["pass_fds"] = pass_fds 1991 elif PY2 and pass_fds: 1992 kwargs['close_fds'] = False 1993 1994 # store the pwd 1995 prev_dir = os.getcwd() 1996 1997 # make sure we're in the right working directory 1998 if cwd: 1999 if os.path.isdir(cwd): 2000 cwd = to_bytes(os.path.abspath(os.path.expanduser(cwd)), errors='surrogate_or_strict') 2001 kwargs['cwd'] = cwd 2002 try: 2003 os.chdir(cwd) 2004 except (OSError, IOError) as e: 2005 self.fail_json(rc=e.errno, msg="Could not chdir to %s, %s" % (cwd, to_native(e)), 2006 exception=traceback.format_exc()) 2007 elif not ignore_invalid_cwd: 2008 self.fail_json(msg="Provided cwd is not a valid directory: %s" % cwd) 2009 2010 old_umask = None 2011 if umask: 2012 old_umask = os.umask(umask) 2013 2014 try: 2015 if self._debug: 2016 self.log('Executing: ' + self._clean_args(args)) 2017 cmd = subprocess.Popen(args, **kwargs) 2018 if before_communicate_callback: 2019 before_communicate_callback(cmd) 2020 2021 # the communication logic here is essentially taken from that 2022 # of the _communicate() function in ssh.py 2023 2024 stdout = b'' 2025 stderr = b'' 2026 try: 2027 selector = selectors.DefaultSelector() 2028 except (IOError, OSError): 2029 # Failed to detect default selector for the given platform 2030 # Select PollSelector which is supported by major platforms 2031 selector = selectors.PollSelector() 2032 2033 selector.register(cmd.stdout, selectors.EVENT_READ) 2034 selector.register(cmd.stderr, selectors.EVENT_READ) 2035 if os.name == 'posix': 2036 fcntl.fcntl(cmd.stdout.fileno(), fcntl.F_SETFL, fcntl.fcntl(cmd.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK) 2037 fcntl.fcntl(cmd.stderr.fileno(), fcntl.F_SETFL, fcntl.fcntl(cmd.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK) 2038 2039 if data: 2040 if not binary_data: 2041 data += '\n' 2042 if isinstance(data, text_type): 2043 data = to_bytes(data) 2044 cmd.stdin.write(data) 2045 cmd.stdin.close() 2046 2047 while True: 2048 events = selector.select(1) 2049 for key, event in events: 2050 b_chunk = key.fileobj.read() 2051 if b_chunk == b(''): 2052 selector.unregister(key.fileobj) 2053 if key.fileobj == cmd.stdout: 2054 stdout += b_chunk 2055 elif key.fileobj == cmd.stderr: 2056 stderr += b_chunk 2057 # if we're checking for prompts, do it now 2058 if prompt_re: 2059 if prompt_re.search(stdout) and not data: 2060 if encoding: 2061 stdout = to_native(stdout, encoding=encoding, errors=errors) 2062 return (257, stdout, "A prompt was encountered while running a command, but no input data was specified") 2063 # only break out if no pipes are left to read or 2064 # the pipes are completely read and 2065 # the process is terminated 2066 if (not events or not selector.get_map()) and cmd.poll() is not None: 2067 break 2068 # No pipes are left to read but process is not yet terminated 2069 # Only then it is safe to wait for the process to be finished 2070 # NOTE: Actually cmd.poll() is always None here if no selectors are left 2071 elif not selector.get_map() and cmd.poll() is None: 2072 cmd.wait() 2073 # The process is terminated. Since no pipes to read from are 2074 # left, there is no need to call select() again. 2075 break 2076 2077 cmd.stdout.close() 2078 cmd.stderr.close() 2079 selector.close() 2080 2081 rc = cmd.returncode 2082 except (OSError, IOError) as e: 2083 self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(e))) 2084 self.fail_json(rc=e.errno, stdout=b'', stderr=b'', msg=to_native(e), cmd=self._clean_args(args)) 2085 except Exception as e: 2086 self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(traceback.format_exc()))) 2087 self.fail_json(rc=257, stdout=b'', stderr=b'', msg=to_native(e), exception=traceback.format_exc(), cmd=self._clean_args(args)) 2088 2089 # Restore env settings 2090 for key, val in old_env_vals.items(): 2091 if val is None: 2092 del os.environ[key] 2093 else: 2094 os.environ[key] = val 2095 2096 if old_umask: 2097 os.umask(old_umask) 2098 2099 if rc != 0 and check_rc: 2100 msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values) 2101 self.fail_json(cmd=self._clean_args(args), rc=rc, stdout=stdout, stderr=stderr, msg=msg) 2102 2103 # reset the pwd 2104 os.chdir(prev_dir) 2105 2106 if encoding is not None: 2107 return (rc, to_native(stdout, encoding=encoding, errors=errors), 2108 to_native(stderr, encoding=encoding, errors=errors)) 2109 2110 return (rc, stdout, stderr) 2111 2112 def append_to_file(self, filename, str): 2113 filename = os.path.expandvars(os.path.expanduser(filename)) 2114 fh = open(filename, 'a') 2115 fh.write(str) 2116 fh.close() 2117 2118 def bytes_to_human(self, size): 2119 return bytes_to_human(size) 2120 2121 # for backwards compatibility 2122 pretty_bytes = bytes_to_human 2123 2124 def human_to_bytes(self, number, isbits=False): 2125 return human_to_bytes(number, isbits) 2126 2127 # 2128 # Backwards compat 2129 # 2130 2131 # In 2.0, moved from inside the module to the toplevel 2132 is_executable = is_executable 2133 2134 @staticmethod 2135 def get_buffer_size(fd): 2136 try: 2137 # 1032 == FZ_GETPIPE_SZ 2138 buffer_size = fcntl.fcntl(fd, 1032) 2139 except Exception: 2140 try: 2141 # not as exact as above, but should be good enough for most platforms that fail the previous call 2142 buffer_size = select.PIPE_BUF 2143 except Exception: 2144 buffer_size = 9000 # use sane default JIC 2145 2146 return buffer_size 2147 2148 2149def get_module_path(): 2150 return os.path.dirname(os.path.realpath(__file__)) 2151