2013-10-31 21:52:37 +01:00
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
2016-02-04 22:54:03 +01:00
#
2013-10-31 21:52:37 +01:00
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
# All rights reserved.
#
2016-02-04 22:54:03 +01:00
# Redistribution and use in source and binary forms, with or without modification,
2013-10-31 21:52:37 +01:00
# are permitted provided that the following conditions are met:
#
2016-02-04 22:54:03 +01:00
# * Redistributions of source code must retain the above copyright
2013-10-31 21:52:37 +01:00
# notice, this list of conditions and the following disclaimer.
2016-02-04 22:54:03 +01:00
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
2013-10-31 21:52:37 +01:00
# and/or other materials provided with the distribution.
#
2016-02-04 22:54:03 +01:00
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
2013-10-31 21:52:37 +01:00
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
2016-08-04 16:47:05 +02:00
BOOLEANS_TRUE = [ ' y ' , ' yes ' , ' on ' , ' 1 ' , ' true ' , 1 , True ]
BOOLEANS_FALSE = [ ' n ' , ' no ' , ' off ' , ' 0 ' , ' false ' , 0 , False ]
2013-10-31 21:52:37 +01:00
BOOLEANS = BOOLEANS_TRUE + BOOLEANS_FALSE
2016-08-24 21:04:20 +02:00
SIZE_RANGES = { ' Y ' : 1 << 80 , ' Z ' : 1 << 70 , ' E ' : 1 << 60 , ' P ' : 1 << 50 , ' T ' : 1 << 40 , ' G ' : 1 << 30 , ' M ' : 1 << 20 , ' K ' : 1 << 10 , ' B ' : 1 }
2013-10-31 21:52:37 +01:00
# ansible modules can be written in any language. To simplify
2016-04-05 20:06:17 +02:00
# development of Python modules, the functions available here can
# be used to do many common tasks
2013-10-31 21:52:37 +01:00
2014-05-19 17:26:06 +02:00
import locale
2013-10-31 21:52:37 +01:00
import os
import re
2014-03-06 20:33:18 +01:00
import pipes
2013-10-31 21:52:37 +01:00
import shlex
import subprocess
import sys
import types
import time
2014-08-04 22:32:41 +02:00
import select
2013-10-31 21:52:37 +01:00
import shutil
import stat
2014-03-20 11:12:58 +01:00
import tempfile
2013-10-31 21:52:37 +01:00
import traceback
import grp
import pwd
import platform
import errno
2015-12-27 12:31:59 +01:00
import datetime
2015-10-20 07:32:21 +02:00
from itertools import repeat , chain
2015-09-23 08:59:59 +02:00
2015-10-14 15:12:02 +02:00
try :
import syslog
HAS_SYSLOG = True
except ImportError :
HAS_SYSLOG = False
2015-09-23 08:59:59 +02:00
try :
2016-08-20 17:08:59 +02:00
from systemd import journal
has_journal = True
2015-09-23 08:59:59 +02:00
except ImportError :
2016-08-20 17:08:59 +02:00
has_journal = False
2016-03-01 22:55:01 +01:00
2016-08-20 17:08:59 +02:00
HAVE_SELINUX = False
2015-10-20 07:32:21 +02:00
try :
2016-08-20 17:08:59 +02:00
import selinux
HAVE_SELINUX = True
except ImportError :
pass
2015-10-20 07:32:21 +02:00
# Python2 & 3 way to get NoneType
NoneType = type ( None )
try :
from collections import Sequence , Mapping
except ImportError :
# python2.5
Sequence = ( list , tuple )
Mapping = ( dict , )
2015-09-30 08:08:41 +02:00
2016-09-01 13:19:03 +02:00
# Note: When getting Sequence from collections, it matches with strings. If
# this matters, make sure to check for strings before checking for sequencetype
2016-05-23 21:17:28 +02:00
try :
from collections . abc import KeysView
SEQUENCETYPE = ( Sequence , KeysView )
except :
SEQUENCETYPE = Sequence
2013-10-31 21:52:37 +01:00
try :
import json
2015-07-20 21:33:07 +02:00
# Detect the python-json library which is incompatible
# Look for simplejson if that's the case
try :
if not isinstance ( json . loads , types . FunctionType ) or not isinstance ( json . dumps , types . FunctionType ) :
raise ImportError
except AttributeError :
raise ImportError
2013-10-31 21:52:37 +01:00
except ImportError :
try :
import simplejson as json
except ImportError :
2016-04-08 17:18:35 +02:00
print ( ' \n { " msg " : " Error: ansible requires the stdlib json or simplejson module, neither was found! " , " failed " : true} ' )
2013-10-31 21:52:37 +01:00
sys . exit ( 1 )
except SyntaxError :
2016-04-08 17:18:35 +02:00
print ( ' \n { " msg " : " SyntaxError: probably due to installed simplejson being for a different python version " , " failed " : true} ' )
2013-10-31 21:52:37 +01:00
sys . exit ( 1 )
2015-08-06 23:39:31 +02:00
AVAILABLE_HASH_ALGORITHMS = dict ( )
try :
import hashlib
# python 2.7.9+ and 2.7.0+
for attribute in ( ' available_algorithms ' , ' algorithms ' ) :
algorithms = getattr ( hashlib , attribute , None )
if algorithms :
break
if algorithms is None :
# python 2.5+
algorithms = ( ' md5 ' , ' sha1 ' , ' sha224 ' , ' sha256 ' , ' sha384 ' , ' sha512 ' )
for algorithm in algorithms :
AVAILABLE_HASH_ALGORITHMS [ algorithm ] = getattr ( hashlib , algorithm )
except ImportError :
import sha
AVAILABLE_HASH_ALGORITHMS = { ' sha1 ' : sha . sha }
try :
import md5
AVAILABLE_HASH_ALGORITHMS [ ' md5 ' ] = md5 . md5
except ImportError :
pass
2016-08-23 22:13:44 +02:00
from ansible . module_utils . pycompat24 import get_exception , literal_eval
2016-08-20 17:08:59 +02:00
from ansible . module_utils . six import ( PY2 , PY3 , b , binary_type , integer_types ,
iteritems , text_type , string_types )
from ansible . module_utils . six . moves import map , reduce
2016-08-26 10:30:46 +02:00
from ansible . module_utils . _text import to_native , to_bytes , to_text
2016-05-14 16:51:13 +02:00
2016-08-20 17:08:59 +02:00
_NUMBERTYPES = tuple ( list ( integer_types ) + [ float ] )
# Deprecated compat. Only kept in case another module used these names Using
# ansible.module_utils.six is preferred
NUMBERTYPES = _NUMBERTYPES
imap = map
try :
# Python 2
unicode
except NameError :
# Python 3
unicode = text_type
try :
# Python 2.6+
bytes
except NameError :
# Python 2.4
bytes = binary_type
try :
# Python 2
basestring
except NameError :
# Python 3
basestring = string_types
2016-08-23 22:13:44 +02:00
_literal_eval = literal_eval
2016-08-20 17:08:59 +02:00
# End of deprecated names
2016-05-18 19:50:55 +02:00
# Internal global holding passed in params. This is consulted in case
# multiple AnsibleModules are created. Otherwise each AnsibleModule would
# attempt to read from stdin. Other code should not use this directly as it
# is an internal implementation detail
2016-04-20 05:08:01 +02:00
_ANSIBLE_ARGS = None
2013-10-31 21:52:37 +01:00
FILE_COMMON_ARGUMENTS = dict (
src = dict ( ) ,
2016-02-10 19:51:12 +01:00
mode = dict ( type = ' raw ' ) ,
2013-10-31 21:52:37 +01:00
owner = dict ( ) ,
group = dict ( ) ,
seuser = dict ( ) ,
serole = dict ( ) ,
selevel = dict ( ) ,
setype = dict ( ) ,
2014-09-16 19:03:40 +02:00
follow = dict ( type = ' bool ' , default = False ) ,
2013-10-31 21:52:37 +01:00
# not taken by the file module, but other modules call file so it must ignore them.
2014-08-19 17:46:46 +02:00
content = dict ( no_log = True ) ,
2013-10-31 21:52:37 +01:00
backup = dict ( ) ,
force = dict ( ) ,
2014-02-24 21:24:25 +01:00
remote_src = dict ( ) , # used by assemble
2014-06-27 11:54:59 +02:00
regexp = dict ( ) , # used by assemble
2014-04-03 23:29:15 +02:00
delimiter = dict ( ) , # used by assemble
2014-04-02 23:41:11 +02:00
directory_mode = dict ( ) , # used by copy
2016-08-09 18:39:17 +02:00
unsafe_writes = dict ( type = ' bool ' ) , # should be available to any module using atomic_move
2013-10-31 21:52:37 +01:00
)
2015-02-09 19:13:13 +01:00
PASSWD_ARG_RE = re . compile ( r ' ^[-] { 0,2}pass[-]?(word|wd)? ' )
2014-03-10 22:06:52 +01:00
2015-09-23 08:50:46 +02:00
# Can't use 07777 on Python 3, can't use 0o7777 on Python 2.4
PERM_BITS = int ( ' 07777 ' , 8 ) # file mode permission bits
EXEC_PERM_BITS = int ( ' 00111 ' , 8 ) # execute permission bits
DEFAULT_PERM = int ( ' 0666 ' , 8 ) # default file permission bits
2015-09-25 16:46:09 +02:00
2013-10-31 21:52:37 +01:00
def get_platform ( ) :
''' what ' s the platform? example: Linux is a platform. '''
return platform . system ( )
def get_distribution ( ) :
''' return the distribution name '''
if platform . system ( ) == ' Linux ' :
try :
2016-08-25 19:18:42 +02:00
supported_dists = platform . _supported_dists + ( ' arch ' , ' alpine ' )
2014-08-19 12:04:27 +02:00
distribution = platform . linux_distribution ( supported_dists = supported_dists ) [ 0 ] . capitalize ( )
2013-12-05 17:36:54 +01:00
if not distribution and os . path . isfile ( ' /etc/system-release ' ) :
2013-11-28 01:31:25 +01:00
distribution = platform . linux_distribution ( supported_dists = [ ' system ' ] ) [ 0 ] . capitalize ( )
if ' Amazon ' in distribution :
distribution = ' Amazon '
else :
2013-10-31 21:52:37 +01:00
distribution = ' OtherLinux '
except :
# FIXME: MethodMissing, I assume?
distribution = platform . dist ( ) [ 0 ] . capitalize ( )
else :
distribution = None
return distribution
2014-06-16 15:06:50 +02:00
def get_distribution_version ( ) :
''' return the distribution version '''
if platform . system ( ) == ' Linux ' :
try :
distribution_version = platform . linux_distribution ( ) [ 1 ]
2014-07-15 18:04:46 +02:00
if not distribution_version and os . path . isfile ( ' /etc/system-release ' ) :
distribution_version = platform . linux_distribution ( supported_dists = [ ' system ' ] ) [ 1 ]
2014-06-16 15:06:50 +02:00
except :
# FIXME: MethodMissing, I assume?
distribution_version = platform . dist ( ) [ 1 ]
else :
2014-07-28 17:35:33 +02:00
distribution_version = None
2014-06-16 15:06:50 +02:00
return distribution_version
2016-04-23 09:14:20 +02:00
def get_all_subclasses ( cls ) :
'''
used by modules like Hardware or Network fact classes to retrieve all subclasses of a given class .
__subclasses__ return only direct sub classes . This one go down into the class tree .
'''
# Retrieve direct subclasses
subclasses = cls . __subclasses__ ( )
to_visit = list ( subclasses )
# Then visit all subclasses
while to_visit :
for sc in to_visit :
# The current class is now visited, so remove it from list
to_visit . remove ( sc )
# Appending all subclasses to visit and keep a reference of available class
for ssc in sc . __subclasses__ ( ) :
subclasses . append ( ssc )
to_visit . append ( ssc )
return subclasses
2013-10-31 21:52:37 +01:00
def load_platform_subclass ( cls , * args , * * kwargs ) :
'''
used by modules like User to have different implementations based on detected platform . See User
module for an example .
'''
this_platform = get_platform ( )
distribution = get_distribution ( )
subclass = None
# get the most specific superclass for this platform
if distribution is not None :
2016-04-23 09:14:20 +02:00
for sc in get_all_subclasses ( cls ) :
2013-10-31 21:52:37 +01:00
if sc . distribution is not None and sc . distribution == distribution and sc . platform == this_platform :
subclass = sc
if subclass is None :
2016-04-23 09:14:20 +02:00
for sc in get_all_subclasses ( cls ) :
2013-10-31 21:52:37 +01:00
if sc . platform == this_platform and sc . distribution is None :
subclass = sc
if subclass is None :
subclass = cls
return super ( cls , subclass ) . __new__ ( subclass )
2014-10-08 20:30:36 +02:00
2016-09-01 13:19:03 +02:00
def json_dict_unicode_to_bytes ( d , encoding = ' utf-8 ' , errors = ' surrogate_or_strict ' ) :
2014-10-08 20:30:36 +02:00
''' Recursively convert dict keys and values to byte str
Specialized for json return because this only handles , lists , tuples ,
and dict container types ( the containers that the json module returns )
'''
2016-08-20 17:08:59 +02:00
if isinstance ( d , text_type ) :
2016-09-01 13:19:03 +02:00
return to_bytes ( d , encoding = encoding , errors = errors )
2014-10-08 20:30:36 +02:00
elif isinstance ( d , dict ) :
2016-09-01 13:19:03 +02:00
return dict ( map ( json_dict_unicode_to_bytes , iteritems ( d ) , repeat ( encoding ) , repeat ( errors ) ) )
2014-10-08 20:30:36 +02:00
elif isinstance ( d , list ) :
2016-09-01 13:19:03 +02:00
return list ( map ( json_dict_unicode_to_bytes , d , repeat ( encoding ) , repeat ( errors ) ) )
2014-10-08 20:30:36 +02:00
elif isinstance ( d , tuple ) :
2016-09-01 13:19:03 +02:00
return tuple ( map ( json_dict_unicode_to_bytes , d , repeat ( encoding ) , repeat ( errors ) ) )
2014-10-08 20:30:36 +02:00
else :
return d
2016-09-01 13:19:03 +02:00
def json_dict_bytes_to_unicode ( d , encoding = ' utf-8 ' , errors = ' surrogate_or_strict ' ) :
2015-01-27 05:37:20 +01:00
''' Recursively convert dict keys and values to byte str
Specialized for json return because this only handles , lists , tuples ,
and dict container types ( the containers that the json module returns )
'''
2016-08-20 17:08:59 +02:00
if isinstance ( d , binary_type ) :
# Warning, can traceback
2016-09-01 13:19:03 +02:00
return to_text ( d , encoding = encoding , errors = errors )
2015-01-27 05:37:20 +01:00
elif isinstance ( d , dict ) :
2016-09-01 13:19:03 +02:00
return dict ( map ( json_dict_bytes_to_unicode , iteritems ( d ) , repeat ( encoding ) , repeat ( errors ) ) )
2015-01-27 05:37:20 +01:00
elif isinstance ( d , list ) :
2016-09-01 13:19:03 +02:00
return list ( map ( json_dict_bytes_to_unicode , d , repeat ( encoding ) , repeat ( errors ) ) )
2015-01-27 05:37:20 +01:00
elif isinstance ( d , tuple ) :
2016-09-01 13:19:03 +02:00
return tuple ( map ( json_dict_bytes_to_unicode , d , repeat ( encoding ) , repeat ( errors ) ) )
2015-01-27 05:37:20 +01:00
else :
return d
2015-10-21 03:51:34 +02:00
def return_values ( obj ) :
2016-08-20 17:08:59 +02:00
""" Return native stringified values from datastructures.
For use with removing sensitive values pre - jsonification . """
if isinstance ( obj , ( text_type , binary_type ) ) :
2015-10-21 03:51:34 +02:00
if obj :
2016-09-01 13:19:03 +02:00
yield to_native ( obj , errors = ' surrogate_or_strict ' )
2015-10-21 03:51:34 +02:00
return
2016-05-23 21:17:28 +02:00
elif isinstance ( obj , SEQUENCETYPE ) :
2015-10-21 03:51:34 +02:00
for element in obj :
for subelement in return_values ( element ) :
yield subelement
elif isinstance ( obj , Mapping ) :
for element in obj . items ( ) :
for subelement in return_values ( element [ 1 ] ) :
yield subelement
elif isinstance ( obj , ( bool , NoneType ) ) :
# This must come before int because bools are also ints
return
elif isinstance ( obj , NUMBERTYPES ) :
2016-09-01 13:19:03 +02:00
yield to_native ( obj , nonstring = ' simplerepr ' )
2015-10-21 03:51:34 +02:00
else :
raise TypeError ( ' Unknown parameter type: %s , %s ' % ( type ( obj ) , obj ) )
def remove_values ( value , no_log_strings ) :
""" Remove strings in no_log_strings from value. If value is a container
type , then remove a lot more """
2016-08-20 17:08:59 +02:00
if isinstance ( value , ( text_type , binary_type ) ) :
# Need native str type
native_str_value = value
if isinstance ( value , text_type ) :
value_is_text = True
if PY2 :
2016-09-01 13:19:03 +02:00
native_str_value = to_bytes ( value , encoding = ' utf-8 ' , errors = ' surrogate_or_strict ' )
2016-08-20 17:08:59 +02:00
elif isinstance ( value , binary_type ) :
value_is_text = False
if PY3 :
2016-09-01 13:19:03 +02:00
native_str_value = to_text ( value , encoding = ' utf-8 ' , errors = ' surrogate_or_strict ' )
2016-08-20 17:08:59 +02:00
if native_str_value in no_log_strings :
2015-10-21 03:51:34 +02:00
return ' VALUE_SPECIFIED_IN_NO_LOG_PARAMETER '
for omit_me in no_log_strings :
2016-08-20 17:08:59 +02:00
native_str_value = native_str_value . replace ( omit_me , ' * ' * 8 )
if value_is_text and isinstance ( native_str_value , binary_type ) :
2016-09-01 13:19:03 +02:00
value = to_text ( native_str_value , encoding = ' utf-8 ' , errors = ' surrogate_or_replace ' )
2016-08-20 17:08:59 +02:00
elif not value_is_text and isinstance ( native_str_value , text_type ) :
2016-09-01 13:19:03 +02:00
value = to_bytes ( native_str_value , encoding = ' utf-8 ' , errors = ' surrogate_or_replace ' )
2015-12-03 05:52:58 +01:00
else :
2016-08-20 17:08:59 +02:00
value = native_str_value
2016-05-23 21:17:28 +02:00
elif isinstance ( value , SEQUENCETYPE ) :
2015-10-21 03:51:34 +02:00
return [ remove_values ( elem , no_log_strings ) for elem in value ]
elif isinstance ( value , Mapping ) :
return dict ( ( k , remove_values ( v , no_log_strings ) ) for k , v in value . items ( ) )
elif isinstance ( value , tuple ( chain ( NUMBERTYPES , ( bool , NoneType ) ) ) ) :
2016-09-01 13:19:03 +02:00
stringy_value = to_native ( value , encoding = ' utf-8 ' , errors = ' surrogate_or_strict ' )
2015-10-21 03:51:34 +02:00
if stringy_value in no_log_strings :
return ' VALUE_SPECIFIED_IN_NO_LOG_PARAMETER '
for omit_me in no_log_strings :
if omit_me in stringy_value :
return ' VALUE_SPECIFIED_IN_NO_LOG_PARAMETER '
2015-12-27 12:31:59 +01:00
elif isinstance ( value , datetime . datetime ) :
value = value . isoformat ( )
2015-10-21 03:51:34 +02:00
else :
raise TypeError ( ' Value of unknown type: %s , %s ' % ( type ( value ) , value ) )
return value
2015-12-27 12:31:59 +01:00
2015-10-21 03:51:34 +02:00
def heuristic_log_sanitize ( data , no_log_values = None ) :
2015-02-09 19:13:13 +01:00
''' Remove strings that look like passwords from log messages '''
# Currently filters:
# user:pass@foo/whatever and http://username:pass@wherever/foo
# This code has false positives and consumes parts of logs that are
# not passwds
# begin: start of a passwd containing string
# end: end of a passwd containing string
# sep: char between user and passwd
# prev_begin: where in the overall string to start a search for
# a passwd
# sep_search_end: where in the string to end a search for the sep
Fix errors when using -vvvv with python 3 (#17186)
Traceback (most recent call last):
File "/tmp/ansible_tpehdgt7/ansible_module_setup.py", line 134, in <module>
main()
File "/tmp/ansible_tpehdgt7/ansible_module_setup.py", line 124, in main
supports_check_mode = True,
File "/tmp/ansible_tpehdgt7/ansible_modlib.zip/ansible/module_utils/basic.py", line 696, in __init__
File "/tmp/ansible_tpehdgt7/ansible_modlib.zip/ansible/module_utils/basic.py", line 1670, in _log_invocation
File "/tmp/ansible_tpehdgt7/ansible_modlib.zip/ansible/module_utils/basic.py", line 469, in heuristic_log_sanitize
TypeError: 'str' does not support the buffer interface
2016-08-23 15:38:04 +02:00
data = to_native ( data )
2015-02-09 19:13:13 +01:00
output = [ ]
begin = len ( data )
prev_begin = begin
sep = 1
while sep :
# Find the potential end of a passwd
try :
end = data . rindex ( ' @ ' , 0 , begin )
except ValueError :
# No passwd in the rest of the data
output . insert ( 0 , data [ 0 : begin ] )
break
# Search for the beginning of a passwd
sep = None
sep_search_end = end
while not sep :
# URL-style username+password
try :
begin = data . rindex ( ' :// ' , 0 , sep_search_end )
except ValueError :
# No url style in the data, check for ssh style in the
# rest of the string
begin = 0
# Search for separator
try :
sep = data . index ( ' : ' , begin + 3 , end )
except ValueError :
# No separator; choices:
if begin == 0 :
# Searched the whole string so there's no password
# here. Return the remaining data
output . insert ( 0 , data [ 0 : begin ] )
break
# Search for a different beginning of the password field.
sep_search_end = begin
continue
if sep :
# Password was found; remove it.
output . insert ( 0 , data [ end : prev_begin ] )
output . insert ( 0 , ' ******** ' )
output . insert ( 0 , data [ begin : sep + 1 ] )
prev_begin = begin
2015-10-21 03:51:34 +02:00
output = ' ' . join ( output )
if no_log_values :
output = remove_values ( output , no_log_values )
return output
2014-10-08 20:30:36 +02:00
2016-08-24 21:04:20 +02:00
def bytes_to_human ( size , isbits = False , unit = None ) :
base = ' Bytes '
if isbits :
base = ' bits '
suffix = ' '
for suffix , limit in sorted ( iteritems ( SIZE_RANGES ) , key = lambda item : - item [ 1 ] ) :
if ( unit is None and size > = limit ) or unit is not None and unit . upper ( ) == suffix [ 0 ] :
break
if limit != 1 :
suffix + = base [ 0 ]
else :
suffix = base
return ' %.2f %s ' % ( float ( size ) / limit , suffix )
def human_to_bytes ( number , default_unit = None , isbits = False ) :
'''
Convert number in string format into bytes ( ex : ' 2K ' = > 2048 ) or using unit argument
ex :
human_to_bytes ( ' 10M ' ) < = > human_to_bytes ( 10 , ' M ' )
'''
m = re . search ( ' ^ \ s*( \ d* \ .? \ d*) \ s*([A-Za-z]+)? ' , str ( number ) , flags = re . IGNORECASE )
if m is None :
raise ValueError ( " human_to_bytes() can ' t interpret following string: %s " % str ( number ) )
try :
num = float ( m . group ( 1 ) )
except :
raise ValueError ( " human_to_bytes() can ' t interpret following number: %s (original input string: %s ) " % ( m . group ( 1 ) , number ) )
unit = m . group ( 2 )
if unit is None :
unit = default_unit
if unit is None :
''' No unit given, returning raw number '''
return int ( round ( num ) )
range_key = unit [ 0 ] . upper ( )
try :
limit = SIZE_RANGES [ range_key ]
except :
raise ValueError ( " human_to_bytes() failed to convert %s (unit = %s ). The suffix must be one of %s " % ( number , unit , " , " . join ( SIZE_RANGES . keys ( ) ) ) )
# default value
unit_class = ' B '
unit_class_name = ' byte '
# handling bits case
if isbits :
unit_class = ' b '
unit_class_name = ' bit '
# check unit value if more than one character (KB, MB)
if len ( unit ) > 1 :
expect_message = ' expect %s %s or %s ' % ( range_key , unit_class , range_key )
if range_key == ' B ' :
expect_message = ' expect %s or %s ' % ( unit_class , unit_class_name )
if unit_class_name in unit . lower ( ) :
pass
elif unit [ 1 ] != unit_class :
raise ValueError ( " human_to_bytes() failed to convert %s . Value is not a valid string ( %s ) " % ( number , expect_message ) )
return int ( round ( num * limit ) )
2015-09-25 16:46:09 +02:00
def is_executable ( path ) :
2016-06-13 18:41:43 +02:00
''' is the given path executable?
Limitations :
* Does not account for FSACLs .
* Most times we really want to know " Can the current user execute this
file " This function does not tell us that, only if an execute bit is set.
'''
# These are all bitfields so first bitwise-or all the permissions we're
# looking for, then bitwise-and with the file's mode to determine if any
# execute bits are set.
return ( ( stat . S_IXUSR | stat . S_IXGRP | stat . S_IXOTH ) & os . stat ( path ) [ stat . ST_MODE ] )
2013-10-31 21:52:37 +01:00
2016-05-18 19:50:55 +02:00
def _load_params ( ) :
''' read the modules parameters and store them globally.
2015-09-25 16:46:09 +02:00
2016-05-18 19:50:55 +02:00
This function may be needed for certain very dynamic custom modules which
want to process the parameters that are being handed the module . Since
this is so closely tied to the implementation of modules we cannot
guarantee API stability for it ( it may change between versions ) however we
will try not to break it gratuitously . It is certainly more future - proof
to call this function and consume its outputs than to implement the logic
inside it as a copy in your own code .
'''
global _ANSIBLE_ARGS
if _ANSIBLE_ARGS is not None :
buffer = _ANSIBLE_ARGS
else :
# debug overrides to read args from file or cmdline
# Avoid tracebacks when locale is non-utf8
# We control the args and we pass them as utf8
if len ( sys . argv ) > 1 :
if os . path . isfile ( sys . argv [ 1 ] ) :
fd = open ( sys . argv [ 1 ] , ' rb ' )
buffer = fd . read ( )
fd . close ( )
else :
buffer = sys . argv [ 1 ]
2016-06-05 01:19:57 +02:00
if PY3 :
2016-05-18 19:50:55 +02:00
buffer = buffer . encode ( ' utf-8 ' , errors = ' surrogateescape ' )
# default case, read from stdin
else :
2016-06-05 01:19:57 +02:00
if PY2 :
2016-05-18 19:50:55 +02:00
buffer = sys . stdin . read ( )
else :
buffer = sys . stdin . buffer . read ( )
_ANSIBLE_ARGS = buffer
try :
params = json . loads ( buffer . decode ( ' utf-8 ' ) )
except ValueError :
# This helper used too early for fail_json to work.
print ( ' \n { " msg " : " Error: Module unable to decode valid JSON on stdin. Unable to figure out what parameters were passed " , " failed " : true} ' )
sys . exit ( 1 )
2016-06-05 01:19:57 +02:00
if PY2 :
2016-05-18 19:50:55 +02:00
params = json_dict_unicode_to_bytes ( params )
try :
return params [ ' ANSIBLE_MODULE_ARGS ' ]
except KeyError :
# This helper does not have access to fail_json so we have to print
# json output on our own.
print ( ' \n { " msg " : " Error: Module unable to locate ANSIBLE_MODULE_ARGS in json data from stdin. Unable to figure out what parameters were passed " , " failed " : true} ' )
sys . exit ( 1 )
2016-03-31 20:17:49 +02:00
def env_fallback ( * args , * * kwargs ) :
''' Load value from environment '''
for arg in args :
if arg in os . environ :
return os . environ [ arg ]
else :
raise AnsibleFallbackNotFound
2016-08-05 15:49:34 +02:00
def _lenient_lowercase ( lst ) :
""" Lowercase elements of a list.
If an element is not a string , pass it through untouched .
"""
lowered = [ ]
for value in lst :
try :
lowered . append ( value . lower ( ) )
except AttributeError :
lowered . append ( value )
return lowered
2016-03-31 20:17:49 +02:00
2016-05-18 19:50:55 +02:00
class AnsibleFallbackNotFound ( Exception ) :
pass
2015-09-25 16:46:09 +02:00
class AnsibleModule ( object ) :
2013-10-31 21:52:37 +01:00
def __init__ ( self , argument_spec , bypass_checks = False , no_log = False ,
check_invalid_arguments = True , mutually_exclusive = None , required_together = None ,
2014-10-26 18:41:58 +01:00
required_one_of = None , add_file_common_args = False , supports_check_mode = False ,
required_if = None ) :
2013-10-31 21:52:37 +01:00
'''
common code for quickly building an ansible module in Python
( although you can write modules in anything that can return JSON )
see library / * for examples
'''
self . argument_spec = argument_spec
self . supports_check_mode = supports_check_mode
self . check_mode = False
2014-01-31 23:09:10 +01:00
self . no_log = no_log
2014-05-13 20:52:38 +02:00
self . cleanup_files = [ ]
2015-10-01 06:09:15 +02:00
self . _debug = False
2016-01-12 19:17:02 +01:00
self . _diff = False
self . _verbosity = 0
2016-02-07 21:45:03 +01:00
# May be used to set modifications to the environment for any
# run_command invocation
self . run_command_environ_update = { }
2015-06-29 17:05:58 +02:00
2013-10-31 21:52:37 +01:00
self . aliases = { }
2016-06-10 17:48:54 +02:00
self . _legal_inputs = [ ' _ansible_check_mode ' , ' _ansible_no_log ' , ' _ansible_debug ' , ' _ansible_diff ' , ' _ansible_verbosity ' , ' _ansible_selinux_special_fs ' , ' _ansible_module_name ' , ' _ansible_version ' , ' _ansible_syslog_facility ' ]
2015-06-29 17:05:58 +02:00
2013-10-31 21:52:37 +01:00
if add_file_common_args :
2015-09-30 08:09:25 +02:00
for k , v in FILE_COMMON_ARGUMENTS . items ( ) :
2013-10-31 21:52:37 +01:00
if k not in self . argument_spec :
self . argument_spec [ k ] = v
2016-04-05 20:06:17 +02:00
self . _load_params ( )
2016-03-31 20:17:49 +02:00
self . _set_fallbacks ( )
2015-10-21 03:51:34 +02:00
2015-12-22 23:15:58 +01:00
# append to legal_inputs and then possibly check against them
try :
self . aliases = self . _handle_aliases ( )
2015-12-23 11:44:30 +01:00
except Exception :
e = get_exception ( )
2016-03-23 09:22:18 +01:00
# Use exceptions here because it isn't safe to call fail_json until no_log is processed
2016-04-08 17:18:35 +02:00
print ( ' \n { " failed " : true, " msg " : " Module alias error: %s " } ' % str ( e ) )
2015-12-22 23:15:58 +01:00
sys . exit ( 1 )
2015-10-21 03:51:34 +02:00
# Save parameter values that should never be logged
self . no_log_values = set ( )
# Use the argspec to determine which args are no_log
for arg_name , arg_opts in self . argument_spec . items ( ) :
if arg_opts . get ( ' no_log ' , False ) :
# Find the value for the no_log'd param
no_log_object = self . params . get ( arg_name , None )
if no_log_object :
self . no_log_values . update ( return_values ( no_log_object ) )
2016-01-20 18:04:44 +01:00
# check the locale as set by the current environment, and reset to
# a known valid (LANG=C) if it's an invalid/unavailable locale
2014-05-19 17:26:06 +02:00
self . _check_locale ( )
2015-09-26 05:57:03 +02:00
self . _check_arguments ( check_invalid_arguments )
2013-10-31 21:52:37 +01:00
2015-09-26 05:57:03 +02:00
# check exclusive early
2014-02-07 19:42:08 +01:00
if not bypass_checks :
self . _check_mutually_exclusive ( mutually_exclusive )
2013-10-31 21:52:37 +01:00
self . _set_defaults ( pre = True )
2015-06-29 17:05:58 +02:00
self . _CHECK_ARGUMENT_TYPES_DISPATCHER = {
' str ' : self . _check_type_str ,
' list ' : self . _check_type_list ,
' dict ' : self . _check_type_dict ,
' bool ' : self . _check_type_bool ,
' int ' : self . _check_type_int ,
' float ' : self . _check_type_float ,
' path ' : self . _check_type_path ,
2016-02-10 19:51:12 +01:00
' raw ' : self . _check_type_raw ,
2016-05-03 16:21:00 +02:00
' jsonarg ' : self . _check_type_jsonarg ,
2016-07-26 15:50:21 +02:00
' json ' : self . _check_type_jsonarg ,
2016-08-16 19:45:41 +02:00
' bytes ' : self . _check_type_bytes ,
' bits ' : self . _check_type_bits ,
2015-06-29 17:05:58 +02:00
}
2013-10-31 21:52:37 +01:00
if not bypass_checks :
self . _check_required_arguments ( )
self . _check_argument_types ( )
2015-07-06 01:55:11 +02:00
self . _check_argument_values ( )
2013-10-31 21:52:37 +01:00
self . _check_required_together ( required_together )
self . _check_required_one_of ( required_one_of )
2014-10-26 18:41:58 +01:00
self . _check_required_if ( required_if )
2013-10-31 21:52:37 +01:00
self . _set_defaults ( pre = False )
2015-10-21 03:51:34 +02:00
2016-01-13 17:00:17 +01:00
if not self . no_log and self . _verbosity > = 3 :
2013-10-31 21:52:37 +01:00
self . _log_invocation ( )
2014-03-18 16:17:44 +01:00
# finally, make sure we're in a sane working dir
self . _set_cwd ( )
2013-10-31 21:52:37 +01:00
def load_file_common_arguments ( self , params ) :
'''
many modules deal with files , this encapsulates common
options that the file module accepts such that it is directly
available to all modules and they can share code .
'''
path = params . get ( ' path ' , params . get ( ' dest ' , None ) )
if path is None :
return { }
else :
2016-09-01 13:19:03 +02:00
path = os . path . expanduser ( os . path . expandvars ( path ) )
2013-10-31 21:52:37 +01:00
2016-09-01 13:19:03 +02:00
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
2014-09-16 19:03:40 +02:00
# if the path is a symlink, and we're following links, get
# the target of the link instead for testing
2016-09-01 13:19:03 +02:00
if params . get ( ' follow ' , False ) and os . path . islink ( b_path ) :
b_path = os . path . realpath ( b_path )
path = to_native ( b_path )
2014-09-16 19:03:40 +02:00
2013-10-31 21:52:37 +01:00
mode = params . get ( ' mode ' , None )
owner = params . get ( ' owner ' , None )
group = params . get ( ' group ' , None )
# selinux related options
seuser = params . get ( ' seuser ' , None )
serole = params . get ( ' serole ' , None )
setype = params . get ( ' setype ' , None )
selevel = params . get ( ' selevel ' , None )
secontext = [ seuser , serole , setype ]
if self . selinux_mls_enabled ( ) :
secontext . append ( selevel )
default_secontext = self . selinux_default_context ( path )
for i in range ( len ( default_secontext ) ) :
if i is not None and secontext [ i ] == ' _default ' :
secontext [ i ] = default_secontext [ i ]
return dict (
path = path , mode = mode , owner = owner , group = group ,
seuser = seuser , serole = serole , setype = setype ,
selevel = selevel , secontext = secontext ,
)
# Detect whether using selinux that is MLS-aware.
# While this means you can set the level/range with
# selinux.lsetfilecon(), it may or may not mean that you
# will get the selevel as part of the context returned
# by selinux.lgetfilecon().
def selinux_mls_enabled ( self ) :
if not HAVE_SELINUX :
return False
if selinux . is_selinux_mls_enabled ( ) == 1 :
return True
else :
return False
def selinux_enabled ( self ) :
if not HAVE_SELINUX :
seenabled = self . get_bin_path ( ' selinuxenabled ' )
if seenabled is not None :
( rc , out , err ) = self . run_command ( seenabled )
if rc == 0 :
self . fail_json ( msg = " Aborting, target uses selinux but python bindings (libselinux-python) aren ' t installed! " )
return False
if selinux . is_selinux_enabled ( ) == 1 :
return True
else :
return False
# Determine whether we need a placeholder for selevel/mls
def selinux_initial_context ( self ) :
context = [ None , None , None ]
if self . selinux_mls_enabled ( ) :
context . append ( None )
return context
# If selinux fails to find a default, return an array of None
def selinux_default_context ( self , path , mode = 0 ) :
context = self . selinux_initial_context ( )
if not HAVE_SELINUX or not self . selinux_enabled ( ) :
return context
try :
2016-08-29 18:11:40 +02:00
ret = selinux . matchpathcon ( to_native ( path , errors = ' strict ' ) , mode )
2013-10-31 21:52:37 +01:00
except OSError :
return context
if ret [ 0 ] == - 1 :
return context
# Limit split to 4 because the selevel, the last in the list,
# may contain ':' characters
context = ret [ 1 ] . split ( ' : ' , 3 )
return context
def selinux_context ( self , path ) :
context = self . selinux_initial_context ( )
if not HAVE_SELINUX or not self . selinux_enabled ( ) :
return context
try :
2016-08-29 18:11:40 +02:00
ret = selinux . lgetfilecon_raw ( to_native ( path , errors = ' strict ' ) )
2015-09-23 08:43:17 +02:00
except OSError :
e = get_exception ( )
2013-10-31 21:52:37 +01:00
if e . errno == errno . ENOENT :
self . fail_json ( path = path , msg = ' path %s does not exist ' % path )
else :
self . fail_json ( path = path , msg = ' failed to retrieve selinux context ' )
if ret [ 0 ] == - 1 :
return context
# Limit split to 4 because the selevel, the last in the list,
# may contain ':' characters
context = ret [ 1 ] . split ( ' : ' , 3 )
return context
def user_and_group ( self , filename ) :
2016-09-01 13:19:03 +02:00
filename = os . path . expanduser ( os . path . expandvars ( filename ) )
b_filename = to_bytes ( filename , errors = ' surrogate_or_strict ' )
st = os . lstat ( b_filename )
2013-10-31 21:52:37 +01:00
uid = st . st_uid
gid = st . st_gid
return ( uid , gid )
2014-04-17 23:16:54 +02:00
def find_mount_point ( self , path ) :
Fixed bug in find_mount_point function
The find_mount_point function does not resolve the mount point of paths with a soft-link correctly and returns the wrong mount-point.
I have mounted an NFS filesystem on /nfs-mount. This directory contains a directory called "directory". I also created a soft-link to this last directory: /soft-link-to-directory -> /nfs-mount/directory. I created the following task to copy a file into /soft-link-to-directory:
- name: copy file to nfs-mount
copy:
src: "file"
dest: "/soft-link-to-directory/file"
This throws an exception:
invalid selinux context: [Errno 95] Operation not supported
This is caused by the find_mount_point function to return '/' as the mount point for '/soft-link-to-directory/file'. This should have been /nfs-mount. Because the find_mount_point returns the wrong mount-point, the is_special_selinux_path function does not recognise the file is on an NFS mount and tries to set the default SELinux context (system_u:object_r:default_t:s0), which fails. The context should have been: system_u:object_r:nfs_t:s0
Full Ansible output:
TASK [copy file to nfs-mount] **************************************************
fatal: [hostname]: FAILED! => {"changed": false, "checksum": "f34b60930a5d6d689cf49a4c16bd7f9806be608c", "cur_context": ["system_u", "object_r", "nfs_t", "s0"], "failed": true, "gid": 24170, "group": "foundation", "input_was": ["system_u", "object_r", "default_t", "s0"], "mode": "0644", "msg": "invalid selinux context: [Errno 95] Operation not supported", "new_context": ["system_u", "object_r", "default_t", "s0"], "owner": "root", "path": "/soft-link-to-directory/.ansible_tmpWCT6Z4file", "secontext": "system_u:object_r:nfs_t:s0", "size": 37, "state": "file", "uid": 0}
2016-02-24 15:57:16 +01:00
path = os . path . realpath ( os . path . expanduser ( os . path . expandvars ( path ) ) )
2014-04-17 23:16:54 +02:00
while not os . path . ismount ( path ) :
path = os . path . dirname ( path )
return path
2015-05-14 16:50:22 +02:00
def is_special_selinux_path ( self , path ) :
2014-04-17 23:16:54 +02:00
"""
2015-05-14 16:50:22 +02:00
Returns a tuple containing ( True , selinux_context ) if the given path is on a
NFS or other ' special ' fs mount point , otherwise the return will be ( False , None ) .
2014-04-17 23:16:54 +02:00
"""
try :
f = open ( ' /proc/mounts ' , ' r ' )
mount_data = f . readlines ( )
f . close ( )
except :
return ( False , None )
path_mount_point = self . find_mount_point ( path )
for line in mount_data :
( device , mount_point , fstype , options , rest ) = line . split ( ' ' , 4 )
2015-05-14 16:50:22 +02:00
if path_mount_point == mount_point :
2016-05-13 05:30:05 +02:00
for fs in self . _selinux_special_fs :
2015-05-14 16:50:22 +02:00
if fs in fstype :
special_context = self . selinux_context ( path_mount_point )
return ( True , special_context )
2014-04-17 23:16:54 +02:00
return ( False , None )
2013-10-31 21:52:37 +01:00
def set_default_selinux_context ( self , path , changed ) :
if not HAVE_SELINUX or not self . selinux_enabled ( ) :
return changed
context = self . selinux_default_context ( path )
return self . set_context_if_different ( path , context , False )
2016-01-02 03:52:41 +01:00
def set_context_if_different ( self , path , context , changed , diff = None ) :
2013-10-31 21:52:37 +01:00
if not HAVE_SELINUX or not self . selinux_enabled ( ) :
return changed
cur_context = self . selinux_context ( path )
new_context = list ( cur_context )
# Iterate over the current context instead of the
# argument context, which may have selevel.
2015-05-14 16:50:22 +02:00
( is_special_se , sp_context ) = self . is_special_selinux_path ( path )
if is_special_se :
new_context = sp_context
2014-04-17 23:16:54 +02:00
else :
for i in range ( len ( cur_context ) ) :
if len ( context ) > i :
if context [ i ] is not None and context [ i ] != cur_context [ i ] :
new_context [ i ] = context [ i ]
2015-05-28 08:26:04 +02:00
elif context [ i ] is None :
2014-04-17 23:16:54 +02:00
new_context [ i ] = cur_context [ i ]
2013-11-08 19:17:02 +01:00
2013-10-31 21:52:37 +01:00
if cur_context != new_context :
2016-01-02 03:52:41 +01:00
if diff is not None :
if ' before ' not in diff :
diff [ ' before ' ] = { }
diff [ ' before ' ] [ ' secontext ' ] = cur_context
if ' after ' not in diff :
diff [ ' after ' ] = { }
diff [ ' after ' ] [ ' secontext ' ] = new_context
2013-10-31 21:52:37 +01:00
try :
if self . check_mode :
return True
2016-08-23 01:44:13 +02:00
rc = selinux . lsetfilecon ( to_native ( path ) ,
2013-10-31 21:52:37 +01:00
str ( ' : ' . join ( new_context ) ) )
2015-09-23 08:43:17 +02:00
except OSError :
e = get_exception ( )
2016-08-09 19:16:42 +02:00
self . fail_json ( path = path , msg = ' invalid selinux context: %s ' % str ( e ) , new_context = new_context , cur_context = cur_context , input_was = context )
2013-10-31 21:52:37 +01:00
if rc != 0 :
self . fail_json ( path = path , msg = ' set selinux context failed ' )
changed = True
return changed
2016-01-02 03:52:41 +01:00
def set_owner_if_different ( self , path , owner , changed , diff = None ) :
2016-09-01 13:19:03 +02:00
path = os . path . expanduser ( os . path . expandvars ( path ) )
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
2013-10-31 21:52:37 +01:00
if owner is None :
return changed
orig_uid , orig_gid = self . user_and_group ( path )
try :
uid = int ( owner )
except ValueError :
try :
uid = pwd . getpwnam ( owner ) . pw_uid
except KeyError :
self . fail_json ( path = path , msg = ' chown failed: failed to look up user %s ' % owner )
if orig_uid != uid :
2016-01-02 03:52:41 +01:00
if diff is not None :
if ' before ' not in diff :
diff [ ' before ' ] = { }
diff [ ' before ' ] [ ' owner ' ] = orig_uid
if ' after ' not in diff :
diff [ ' after ' ] = { }
diff [ ' after ' ] [ ' owner ' ] = uid
2013-10-31 21:52:37 +01:00
if self . check_mode :
return True
try :
2016-09-01 13:19:03 +02:00
os . lchown ( b_path , uid , - 1 )
2013-10-31 21:52:37 +01:00
except OSError :
self . fail_json ( path = path , msg = ' chown failed ' )
changed = True
return changed
2016-01-02 03:52:41 +01:00
def set_group_if_different ( self , path , group , changed , diff = None ) :
2016-09-01 13:19:03 +02:00
path = os . path . expanduser ( os . path . expandvars ( path ) )
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
2013-10-31 21:52:37 +01:00
if group is None :
return changed
2016-09-01 13:19:03 +02:00
orig_uid , orig_gid = self . user_and_group ( b_path )
2013-10-31 21:52:37 +01:00
try :
gid = int ( group )
except ValueError :
try :
gid = grp . getgrnam ( group ) . gr_gid
except KeyError :
self . fail_json ( path = path , msg = ' chgrp failed: failed to look up group %s ' % group )
if orig_gid != gid :
2016-01-02 03:52:41 +01:00
if diff is not None :
if ' before ' not in diff :
diff [ ' before ' ] = { }
diff [ ' before ' ] [ ' group ' ] = orig_gid
if ' after ' not in diff :
diff [ ' after ' ] = { }
diff [ ' after ' ] [ ' group ' ] = gid
2013-10-31 21:52:37 +01:00
if self . check_mode :
return True
try :
2016-09-01 13:19:03 +02:00
os . lchown ( b_path , - 1 , gid )
2013-10-31 21:52:37 +01:00
except OSError :
self . fail_json ( path = path , msg = ' chgrp failed ' )
changed = True
return changed
2016-01-02 03:52:41 +01:00
def set_mode_if_different ( self , path , mode , changed , diff = None ) :
2016-09-01 13:19:03 +02:00
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
b_path = os . path . expanduser ( os . path . expandvars ( b_path ) )
2016-08-29 18:11:40 +02:00
path_stat = os . lstat ( b_path )
2013-12-07 03:06:35 +01:00
2013-10-31 21:52:37 +01:00
if mode is None :
return changed
2013-12-07 03:06:35 +01:00
if not isinstance ( mode , int ) :
try :
2013-10-31 21:52:37 +01:00
mode = int ( mode , 8 )
2013-12-07 03:06:35 +01:00
except Exception :
try :
mode = self . _symbolic_mode_to_octal ( path_stat , mode )
2015-09-23 08:43:17 +02:00
except Exception :
e = get_exception ( )
2013-12-07 03:06:35 +01:00
self . fail_json ( path = path ,
msg = " mode must be in octal or symbolic form " ,
details = str ( e ) )
2013-10-31 21:52:37 +01:00
2016-03-04 20:41:35 +01:00
if mode != stat . S_IMODE ( mode ) :
# prevent mode from having extra info orbeing invalid long number
2016-03-04 20:44:03 +01:00
self . fail_json ( path = path , msg = " Invalid mode supplied, only permission info is allowed " , details = mode )
2016-03-04 20:41:35 +01:00
2013-12-07 03:06:35 +01:00
prev_mode = stat . S_IMODE ( path_stat . st_mode )
2013-10-31 21:52:37 +01:00
if prev_mode != mode :
2016-01-02 03:52:41 +01:00
if diff is not None :
if ' before ' not in diff :
diff [ ' before ' ] = { }
2016-08-25 23:58:35 +02:00
diff [ ' before ' ] [ ' mode ' ] = ' 0 %03o ' % prev_mode
2016-01-02 03:52:41 +01:00
if ' after ' not in diff :
diff [ ' after ' ] = { }
2016-08-25 23:58:35 +02:00
diff [ ' after ' ] [ ' mode ' ] = ' 0 %03o ' % mode
2016-01-02 03:52:41 +01:00
2013-10-31 21:52:37 +01:00
if self . check_mode :
return True
# FIXME: comparison against string above will cause this to be executed
# every time
try :
2015-02-16 16:07:58 +01:00
if hasattr ( os , ' lchmod ' ) :
2016-08-29 18:11:40 +02:00
os . lchmod ( b_path , mode )
2013-10-31 21:52:37 +01:00
else :
2016-08-29 18:11:40 +02:00
if not os . path . islink ( b_path ) :
os . chmod ( b_path , mode )
2015-02-16 16:07:58 +01:00
else :
# Attempt to set the perms of the symlink but be
# careful not to change the perms of the underlying
# file while trying
2016-08-29 18:11:40 +02:00
underlying_stat = os . stat ( b_path )
os . chmod ( b_path , mode )
new_underlying_stat = os . stat ( b_path )
2015-02-16 16:07:58 +01:00
if underlying_stat . st_mode != new_underlying_stat . st_mode :
2016-08-29 18:11:40 +02:00
os . chmod ( b_path , stat . S_IMODE ( underlying_stat . st_mode ) )
2015-09-23 08:43:17 +02:00
except OSError :
e = get_exception ( )
2016-08-29 18:11:40 +02:00
if os . path . islink ( b_path ) and e . errno == errno . EPERM : # Can't set mode on symbolic links
2013-10-31 21:52:37 +01:00
pass
2015-02-16 16:07:58 +01:00
elif e . errno in ( errno . ENOENT , errno . ELOOP ) : # Can't set mode on broken symbolic links
2013-10-31 21:52:37 +01:00
pass
else :
raise e
2015-09-23 08:43:17 +02:00
except Exception :
e = get_exception ( )
2013-10-31 21:52:37 +01:00
self . fail_json ( path = path , msg = ' chmod failed ' , details = str ( e ) )
2016-08-29 18:11:40 +02:00
path_stat = os . lstat ( b_path )
2013-12-07 03:06:35 +01:00
new_mode = stat . S_IMODE ( path_stat . st_mode )
2013-10-31 21:52:37 +01:00
if new_mode != prev_mode :
changed = True
return changed
2013-12-07 03:06:35 +01:00
def _symbolic_mode_to_octal ( self , path_stat , symbolic_mode ) :
new_mode = stat . S_IMODE ( path_stat . st_mode )
2016-01-28 21:05:10 +01:00
mode_re = re . compile ( r ' ^(?P<users>[ugoa]+)(?P<operator>[-+=])(?P<perms>[rwxXst-]*|[ugo])$ ' )
2013-12-07 03:06:35 +01:00
for mode in symbolic_mode . split ( ' , ' ) :
match = mode_re . match ( mode )
if match :
users = match . group ( ' users ' )
operator = match . group ( ' operator ' )
perms = match . group ( ' perms ' )
2015-06-24 19:22:37 +02:00
if users == ' a ' :
users = ' ugo '
2013-12-07 03:06:35 +01:00
for user in users :
mode_to_apply = self . _get_octal_mode_from_symbolic_perms ( path_stat , user , perms )
new_mode = self . _apply_operation_to_mode ( user , operator , mode_to_apply , new_mode )
else :
raise ValueError ( " bad symbolic permission for mode: %s " % mode )
return new_mode
2016-02-04 22:54:03 +01:00
2013-12-07 03:06:35 +01:00
def _apply_operation_to_mode ( self , user , operator , mode_to_apply , current_mode ) :
if operator == ' = ' :
if user == ' u ' : mask = stat . S_IRWXU | stat . S_ISUID
elif user == ' g ' : mask = stat . S_IRWXG | stat . S_ISGID
elif user == ' o ' : mask = stat . S_IRWXO | stat . S_ISVTX
2016-02-04 22:54:03 +01:00
# mask out u, g, or o permissions from current_mode and apply new permissions
2015-09-23 08:50:46 +02:00
inverse_mask = mask ^ PERM_BITS
2013-12-07 03:06:35 +01:00
new_mode = ( current_mode & inverse_mask ) | mode_to_apply
elif operator == ' + ' :
new_mode = current_mode | mode_to_apply
elif operator == ' - ' :
new_mode = current_mode - ( current_mode & mode_to_apply )
return new_mode
2016-02-04 22:54:03 +01:00
2013-12-07 03:06:35 +01:00
def _get_octal_mode_from_symbolic_perms ( self , path_stat , user , perms ) :
prev_mode = stat . S_IMODE ( path_stat . st_mode )
2016-02-04 22:54:03 +01:00
2013-12-07 03:06:35 +01:00
is_directory = stat . S_ISDIR ( path_stat . st_mode )
2015-09-23 08:50:46 +02:00
has_x_permissions = ( prev_mode & EXEC_PERM_BITS ) > 0
2013-12-07 03:06:35 +01:00
apply_X_permission = is_directory or has_x_permissions
# Permission bits constants documented at:
# http://docs.python.org/2/library/stat.html#stat.S_ISUID
2014-08-28 02:15:57 +02:00
if apply_X_permission :
X_perms = {
' u ' : { ' X ' : stat . S_IXUSR } ,
' g ' : { ' X ' : stat . S_IXGRP } ,
' o ' : { ' X ' : stat . S_IXOTH }
}
else :
X_perms = {
' u ' : { ' X ' : 0 } ,
' g ' : { ' X ' : 0 } ,
' o ' : { ' X ' : 0 }
}
2013-12-07 03:06:35 +01:00
user_perms_to_modes = {
' u ' : {
' r ' : stat . S_IRUSR ,
' w ' : stat . S_IWUSR ,
' x ' : stat . S_IXUSR ,
' s ' : stat . S_ISUID ,
' t ' : 0 ,
' u ' : prev_mode & stat . S_IRWXU ,
' g ' : ( prev_mode & stat . S_IRWXG ) << 3 ,
' o ' : ( prev_mode & stat . S_IRWXO ) << 6 } ,
' g ' : {
' r ' : stat . S_IRGRP ,
' w ' : stat . S_IWGRP ,
' x ' : stat . S_IXGRP ,
' s ' : stat . S_ISGID ,
' t ' : 0 ,
' u ' : ( prev_mode & stat . S_IRWXU ) >> 3 ,
' g ' : prev_mode & stat . S_IRWXG ,
' o ' : ( prev_mode & stat . S_IRWXO ) << 3 } ,
' o ' : {
' r ' : stat . S_IROTH ,
' w ' : stat . S_IWOTH ,
' x ' : stat . S_IXOTH ,
' s ' : 0 ,
' t ' : stat . S_ISVTX ,
' u ' : ( prev_mode & stat . S_IRWXU ) >> 6 ,
' g ' : ( prev_mode & stat . S_IRWXG ) >> 3 ,
' o ' : prev_mode & stat . S_IRWXO }
}
2014-08-28 02:15:57 +02:00
# Insert X_perms into user_perms_to_modes
for key , value in X_perms . items ( ) :
user_perms_to_modes [ key ] . update ( value )
2013-12-07 03:06:35 +01:00
or_reduce = lambda mode , perm : mode | user_perms_to_modes [ user ] [ perm ]
return reduce ( or_reduce , perms , 0 )
2016-01-02 03:52:41 +01:00
def set_fs_attributes_if_different ( self , file_args , changed , diff = None ) :
2013-10-31 21:52:37 +01:00
# set modes owners and context as needed
changed = self . set_context_if_different (
2016-01-02 03:52:41 +01:00
file_args [ ' path ' ] , file_args [ ' secontext ' ] , changed , diff
2013-10-31 21:52:37 +01:00
)
changed = self . set_owner_if_different (
2016-01-02 03:52:41 +01:00
file_args [ ' path ' ] , file_args [ ' owner ' ] , changed , diff
2013-10-31 21:52:37 +01:00
)
changed = self . set_group_if_different (
2016-01-02 03:52:41 +01:00
file_args [ ' path ' ] , file_args [ ' group ' ] , changed , diff
2013-10-31 21:52:37 +01:00
)
changed = self . set_mode_if_different (
2016-01-02 03:52:41 +01:00
file_args [ ' path ' ] , file_args [ ' mode ' ] , changed , diff
2013-10-31 21:52:37 +01:00
)
return changed
2016-01-02 03:52:41 +01:00
def set_directory_attributes_if_different ( self , file_args , changed , diff = None ) :
return self . set_fs_attributes_if_different ( file_args , changed , diff )
2014-03-14 04:07:35 +01:00
2016-01-02 03:52:41 +01:00
def set_file_attributes_if_different ( self , file_args , changed , diff = None ) :
return self . set_fs_attributes_if_different ( file_args , changed , diff )
2013-10-31 21:52:37 +01:00
def add_path_info ( self , kwargs ) :
'''
for results that are files , supplement the info about the file
in the return path with stats about the file path .
'''
path = kwargs . get ( ' path ' , kwargs . get ( ' dest ' , None ) )
if path is None :
return kwargs
2016-09-01 13:19:03 +02:00
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
if os . path . exists ( b_path ) :
2013-10-31 21:52:37 +01:00
( uid , gid ) = self . user_and_group ( path )
kwargs [ ' uid ' ] = uid
kwargs [ ' gid ' ] = gid
try :
user = pwd . getpwuid ( uid ) [ 0 ]
except KeyError :
user = str ( uid )
try :
group = grp . getgrgid ( gid ) [ 0 ]
except KeyError :
group = str ( gid )
kwargs [ ' owner ' ] = user
kwargs [ ' group ' ] = group
2016-09-01 13:19:03 +02:00
st = os . lstat ( b_path )
2016-08-25 16:44:31 +02:00
kwargs [ ' mode ' ] = ' 0 %03o ' % stat . S_IMODE ( st [ stat . ST_MODE ] )
2013-10-31 21:52:37 +01:00
# secontext not yet supported
2016-09-01 13:19:03 +02:00
if os . path . islink ( b_path ) :
2013-10-31 21:52:37 +01:00
kwargs [ ' state ' ] = ' link '
2016-09-01 13:19:03 +02:00
elif os . path . isdir ( b_path ) :
2013-10-31 21:52:37 +01:00
kwargs [ ' state ' ] = ' directory '
2016-09-01 13:19:03 +02:00
elif os . stat ( b_path ) . st_nlink > 1 :
2013-11-01 14:41:22 +01:00
kwargs [ ' state ' ] = ' hard '
2013-10-31 21:52:37 +01:00
else :
kwargs [ ' state ' ] = ' file '
if HAVE_SELINUX and self . selinux_enabled ( ) :
kwargs [ ' secontext ' ] = ' : ' . join ( self . selinux_context ( path ) )
kwargs [ ' size ' ] = st [ stat . ST_SIZE ]
else :
kwargs [ ' state ' ] = ' absent '
return kwargs
2014-05-19 17:26:06 +02:00
def _check_locale ( self ) :
'''
Uses the locale module to test the currently set locale
( per the LANG and LC_CTYPE environment settings )
'''
try :
# setting the locale to '' uses the default locale
# as it would be returned by locale.getdefaultlocale()
locale . setlocale ( locale . LC_ALL , ' ' )
2015-09-23 08:43:17 +02:00
except locale . Error :
2014-05-19 17:26:06 +02:00
# fallback to the 'C' locale, which may cause unicode
# issues but is preferable to simply failing because
# of an unknown locale
locale . setlocale ( locale . LC_ALL , ' C ' )
2015-10-21 19:59:51 +02:00
os . environ [ ' LANG ' ] = ' C '
2015-10-19 21:25:30 +02:00
os . environ [ ' LC_ALL ' ] = ' C '
2014-11-26 10:35:45 +01:00
os . environ [ ' LC_MESSAGES ' ] = ' C '
2015-09-23 08:43:17 +02:00
except Exception :
e = get_exception ( )
2014-05-19 17:26:06 +02:00
self . fail_json ( msg = " An unknown error was encountered while attempting to validate the locale: %s " % e )
2013-10-31 21:52:37 +01:00
def _handle_aliases ( self ) :
2015-12-22 23:15:58 +01:00
# this uses exceptions as it happens before we can safely call fail_json
2013-10-31 21:52:37 +01:00
aliases_results = { } #alias:canon
2015-09-30 08:09:25 +02:00
for ( k , v ) in self . argument_spec . items ( ) :
2013-10-31 21:52:37 +01:00
self . _legal_inputs . append ( k )
aliases = v . get ( ' aliases ' , None )
default = v . get ( ' default ' , None )
required = v . get ( ' required ' , False )
if default is not None and required :
# not alias specific but this is a good place to check this
2015-12-22 23:15:58 +01:00
raise Exception ( " internal error: required and default are mutually exclusive for %s " % k )
2013-10-31 21:52:37 +01:00
if aliases is None :
continue
2016-09-01 13:19:03 +02:00
if not isinstance ( aliases , SEQUENCETYPE ) or isinstance ( aliases , ( binary_type , text_type ) ) :
raise Exception ( ' internal error: aliases must be a list or tuple ' )
2013-10-31 21:52:37 +01:00
for alias in aliases :
self . _legal_inputs . append ( alias )
aliases_results [ alias ] = k
if alias in self . params :
self . params [ k ] = self . params [ alias ]
2015-09-26 05:57:03 +02:00
2013-10-31 21:52:37 +01:00
return aliases_results
2015-09-26 05:57:03 +02:00
def _check_arguments ( self , check_invalid_arguments ) :
2016-05-13 05:30:05 +02:00
self . _syslog_facility = ' LOG_USER '
2016-05-11 06:56:19 +02:00
for ( k , v ) in list ( self . params . items ( ) ) :
2015-09-26 05:57:03 +02:00
2015-07-01 21:10:25 +02:00
if k == ' _ansible_check_mode ' and v :
self . check_mode = True
2013-10-31 21:52:37 +01:00
2015-09-26 05:57:03 +02:00
elif k == ' _ansible_no_log ' :
2014-01-31 23:09:10 +01:00
self . no_log = self . boolean ( v )
2015-09-26 05:57:03 +02:00
elif k == ' _ansible_debug ' :
2015-10-01 06:09:15 +02:00
self . _debug = self . boolean ( v )
2015-09-26 05:57:03 +02:00
2016-01-12 19:17:02 +01:00
elif k == ' _ansible_diff ' :
self . _diff = self . boolean ( v )
elif k == ' _ansible_verbosity ' :
self . _verbosity = v
2016-05-13 05:30:05 +02:00
elif k == ' _ansible_selinux_special_fs ' :
self . _selinux_special_fs = v
elif k == ' _ansible_syslog_facility ' :
self . _syslog_facility = v
elif k == ' _ansible_version ' :
self . ansible_version = v
2016-06-10 17:48:54 +02:00
elif k == ' _ansible_module_name ' :
self . _name = v
2015-09-26 05:57:03 +02:00
elif check_invalid_arguments and k not in self . _legal_inputs :
2013-10-31 21:52:37 +01:00
self . fail_json ( msg = " unsupported parameter for module: %s " % k )
2016-02-01 21:17:23 +01:00
#clean up internal params:
if k . startswith ( ' _ansible_ ' ) :
del self . params [ k ]
2016-06-10 17:48:54 +02:00
if self . check_mode and not self . supports_check_mode :
self . exit_json ( skipped = True , msg = " remote module ( %s ) does not support check mode " % self . _name )
2013-10-31 21:52:37 +01:00
def _count_terms ( self , check ) :
count = 0
for term in check :
2013-11-01 00:47:05 +01:00
if term in self . params :
count + = 1
2013-10-31 21:52:37 +01:00
return count
def _check_mutually_exclusive ( self , spec ) :
if spec is None :
return
for check in spec :
count = self . _count_terms ( check )
if count > 1 :
2015-05-27 10:20:54 +02:00
self . fail_json ( msg = " parameters are mutually exclusive: %s " % ( check , ) )
2013-10-31 21:52:37 +01:00
def _check_required_one_of ( self , spec ) :
if spec is None :
return
for check in spec :
count = self . _count_terms ( check )
if count == 0 :
self . fail_json ( msg = " one of the following is required: %s " % ' , ' . join ( check ) )
def _check_required_together ( self , spec ) :
if spec is None :
return
for check in spec :
counts = [ self . _count_terms ( [ field ] ) for field in check ]
non_zero = [ c for c in counts if c > 0 ]
if len ( non_zero ) > 0 :
if 0 in counts :
2015-05-27 10:20:54 +02:00
self . fail_json ( msg = " parameters are required together: %s " % ( check , ) )
2013-10-31 21:52:37 +01:00
def _check_required_arguments ( self ) :
''' ensure all required arguments are present '''
missing = [ ]
2015-09-30 08:09:25 +02:00
for ( k , v ) in self . argument_spec . items ( ) :
2013-10-31 21:52:37 +01:00
required = v . get ( ' required ' , False )
if required and k not in self . params :
missing . append ( k )
if len ( missing ) > 0 :
self . fail_json ( msg = " missing required arguments: %s " % " , " . join ( missing ) )
2014-10-26 18:41:58 +01:00
def _check_required_if ( self , spec ) :
''' ensure that parameters which conditionally required are present '''
if spec is None :
return
for ( key , val , requirements ) in spec :
missing = [ ]
if key in self . params and self . params [ key ] == val :
for check in requirements :
2015-07-08 18:45:02 +02:00
count = self . _count_terms ( ( check , ) )
2014-10-26 18:41:58 +01:00
if count == 0 :
missing . append ( check )
if len ( missing ) > 0 :
2015-05-26 20:28:30 +02:00
self . fail_json ( msg = " %s is %s but the following are missing: %s " % ( key , val , ' , ' . join ( missing ) ) )
2014-10-26 18:41:58 +01:00
2013-10-31 21:52:37 +01:00
def _check_argument_values ( self ) :
''' ensure all arguments have the requested values, and there are no stray arguments '''
2015-09-30 08:09:25 +02:00
for ( k , v ) in self . argument_spec . items ( ) :
2013-10-31 21:52:37 +01:00
choices = v . get ( ' choices ' , None )
if choices is None :
continue
2016-09-01 13:19:03 +02:00
if isinstance ( choices , SEQUENCETYPE ) and not isinstance ( choices , ( binary_type , text_type ) ) :
2013-10-31 21:52:37 +01:00
if k in self . params :
if self . params [ k ] not in choices :
2016-09-01 13:19:03 +02:00
# PyYaml converts certain strings to bools. If we can unambiguously convert back, do so before checking
# the value. If we can't figure this out, module author is responsible.
2016-08-05 15:49:34 +02:00
lowered_choices = None
if self . params [ k ] == ' False ' :
lowered_choices = _lenient_lowercase ( choices )
FALSEY = frozenset ( BOOLEANS_FALSE )
overlap = FALSEY . intersection ( choices )
if len ( overlap ) == 1 :
# Extract from a set
( self . params [ k ] , ) = overlap
if self . params [ k ] == ' True ' :
if lowered_choices is None :
lowered_choices = _lenient_lowercase ( choices )
TRUTHY = frozenset ( BOOLEANS_TRUE )
overlap = TRUTHY . intersection ( choices )
if len ( overlap ) == 1 :
( self . params [ k ] , ) = overlap
if self . params [ k ] not in choices :
2016-09-01 13:19:03 +02:00
choices_str = " , " . join ( [ to_native ( c ) for c in choices ] )
2016-08-05 15:49:34 +02:00
msg = " value of %s must be one of: %s , got: %s " % ( k , choices_str , self . params [ k ] )
self . fail_json ( msg = msg )
2013-10-31 21:52:37 +01:00
else :
2016-05-26 19:00:53 +02:00
self . fail_json ( msg = " internal error: choices for argument %s are not iterable: %s " % ( k , choices ) )
2013-10-31 21:52:37 +01:00
2016-08-20 17:08:59 +02:00
def safe_eval ( self , value , locals = None , include_exceptions = False ) :
2013-10-31 23:44:13 +01:00
# do not allow method calls to modules
2016-08-20 17:08:59 +02:00
if not isinstance ( value , string_types ) :
# already templated to a datavaluestructure, perhaps?
2013-10-31 23:44:13 +01:00
if include_exceptions :
2016-08-20 17:08:59 +02:00
return ( value , None )
return value
if re . search ( r ' \ w \ . \ w+ \ ( ' , value ) :
2013-10-31 23:44:13 +01:00
if include_exceptions :
2016-08-20 17:08:59 +02:00
return ( value , None )
return value
2013-10-31 23:44:13 +01:00
# do not allow imports
2016-08-20 17:08:59 +02:00
if re . search ( r ' import \ w+ ' , value ) :
2013-10-31 23:44:13 +01:00
if include_exceptions :
2016-08-20 17:08:59 +02:00
return ( value , None )
return value
2013-10-31 23:44:13 +01:00
try :
2016-08-20 17:08:59 +02:00
result = literal_eval ( value )
2013-10-31 23:44:13 +01:00
if include_exceptions :
return ( result , None )
else :
return result
2015-09-23 08:43:17 +02:00
except Exception :
e = get_exception ( )
2013-10-31 23:44:13 +01:00
if include_exceptions :
2016-08-20 17:08:59 +02:00
return ( value , e )
return value
2013-10-31 23:44:13 +01:00
2015-06-29 17:05:58 +02:00
def _check_type_str ( self , value ) :
2016-08-20 17:08:59 +02:00
if isinstance ( value , string_types ) :
2015-06-29 17:05:58 +02:00
return value
# Note: This could throw a unicode error if value's __str__() method
# returns non-ascii. Have to port utils.to_bytes() if that happens
return str ( value )
def _check_type_list ( self , value ) :
if isinstance ( value , list ) :
return value
2016-08-20 17:08:59 +02:00
if isinstance ( value , string_types ) :
2015-06-29 17:05:58 +02:00
return value . split ( " , " )
elif isinstance ( value , int ) or isinstance ( value , float ) :
return [ str ( value ) ]
raise TypeError ( ' %s cannot be converted to a list ' % type ( value ) )
def _check_type_dict ( self , value ) :
if isinstance ( value , dict ) :
return value
2016-08-20 17:08:59 +02:00
if isinstance ( value , string_types ) :
2015-06-29 17:05:58 +02:00
if value . startswith ( " { " ) :
try :
return json . loads ( value )
except :
( result , exc ) = self . safe_eval ( value , dict ( ) , include_exceptions = True )
if exc is not None :
raise TypeError ( ' unable to evaluate string as dictionary ' )
return result
elif ' = ' in value :
2014-12-22 19:30:36 +01:00
fields = [ ]
field_buffer = [ ]
in_quote = False
in_escape = False
for c in value . strip ( ) :
if in_escape :
field_buffer . append ( c )
in_escape = False
elif c == ' \\ ' :
in_escape = True
elif not in_quote and c in ( ' \' ' , ' " ' ) :
in_quote = c
elif in_quote and in_quote == c :
in_quote = False
elif not in_quote and c in ( ' , ' , ' ' ) :
field = ' ' . join ( field_buffer )
if field :
fields . append ( field )
field_buffer = [ ]
else :
field_buffer . append ( c )
field = ' ' . join ( field_buffer )
if field :
fields . append ( field )
return dict ( x . split ( " = " , 1 ) for x in fields )
2015-06-29 17:05:58 +02:00
else :
raise TypeError ( " dictionary requested, could not parse JSON or key=value " )
raise TypeError ( ' %s cannot be converted to a dict ' % type ( value ) )
def _check_type_bool ( self , value ) :
if isinstance ( value , bool ) :
return value
2016-08-20 17:08:59 +02:00
if isinstance ( value , string_types ) or isinstance ( value , int ) :
2015-06-29 17:05:58 +02:00
return self . boolean ( value )
raise TypeError ( ' %s cannot be converted to a bool ' % type ( value ) )
def _check_type_int ( self , value ) :
if isinstance ( value , int ) :
return value
2016-08-20 17:08:59 +02:00
if isinstance ( value , string_types ) :
2015-06-29 17:05:58 +02:00
return int ( value )
raise TypeError ( ' %s cannot be converted to an int ' % type ( value ) )
def _check_type_float ( self , value ) :
if isinstance ( value , float ) :
return value
2016-08-31 17:57:47 +02:00
if isinstance ( value , ( binary_type , text_type , int ) ) :
2015-06-29 17:05:58 +02:00
return float ( value )
raise TypeError ( ' %s cannot be converted to a float ' % type ( value ) )
def _check_type_path ( self , value ) :
value = self . _check_type_str ( value )
2016-04-06 23:07:48 +02:00
return os . path . expanduser ( os . path . expandvars ( value ) )
2015-06-29 17:05:58 +02:00
2016-05-03 16:21:00 +02:00
def _check_type_jsonarg ( self , value ) :
# Return a jsonified string. Sometimes the controller turns a json
# string into a dict/list so transform it back into json here
2016-08-20 17:08:59 +02:00
if isinstance ( value , ( text_type , binary_type ) ) :
2016-05-09 17:14:08 +02:00
return value . strip ( )
2016-05-03 16:21:00 +02:00
else :
2016-07-28 21:54:09 +02:00
if isinstance ( value , ( list , tuple , dict ) ) :
2016-05-03 16:21:00 +02:00
return json . dumps ( value )
raise TypeError ( ' %s cannot be converted to a json string ' % type ( value ) )
2016-02-10 19:51:12 +01:00
def _check_type_raw ( self , value ) :
return value
2015-06-29 17:05:58 +02:00
2016-08-16 19:45:41 +02:00
def _check_type_bytes ( self , value ) :
try :
self . human_to_bytes ( value )
except ValueError :
raise TypeError ( ' %s cannot be converted to a Byte value ' % type ( value ) )
def _check_type_bits ( self , value ) :
try :
2016-08-24 21:04:20 +02:00
self . human_to_bytes ( value , isbits = True )
2016-08-16 19:45:41 +02:00
except ValueError :
raise TypeError ( ' %s cannot be converted to a Bit value ' % type ( value ) )
2013-10-31 21:52:37 +01:00
def _check_argument_types ( self ) :
''' ensure all arguments have the requested type '''
2015-09-30 08:09:25 +02:00
for ( k , v ) in self . argument_spec . items ( ) :
2013-10-31 21:52:37 +01:00
wanted = v . get ( ' type ' , None )
if k not in self . params :
continue
2016-02-10 19:51:12 +01:00
if wanted is None :
# Mostly we want to default to str.
# For values set to None explicitly, return None instead as
# that allows a user to unset a parameter
if self . params [ k ] is None :
continue
wanted = ' str '
2013-10-31 21:52:37 +01:00
value = self . params [ k ]
2016-03-24 20:49:03 +01:00
if value is None :
continue
2013-10-31 21:52:37 +01:00
2015-05-19 17:34:39 +02:00
try :
2015-06-29 17:05:58 +02:00
type_checker = self . _CHECK_ARGUMENT_TYPES_DISPATCHER [ wanted ]
except KeyError :
self . fail_json ( msg = " implementation error: unknown type %s requested for %s " % ( wanted , k ) )
try :
self . params [ k ] = type_checker ( value )
except ( TypeError , ValueError ) :
self . fail_json ( msg = " argument %s is of type %s and we were unable to convert to %s " % ( k , type ( value ) , wanted ) )
2013-10-31 21:52:37 +01:00
def _set_defaults ( self , pre = True ) :
2015-09-30 08:09:25 +02:00
for ( k , v ) in self . argument_spec . items ( ) :
2013-11-01 00:47:05 +01:00
default = v . get ( ' default ' , None )
if pre == True :
# this prevents setting defaults on required items
if default is not None and k not in self . params :
self . params [ k ] = default
else :
# make sure things without a default still get set None
if k not in self . params :
self . params [ k ] = default
2013-10-31 21:52:37 +01:00
2016-03-31 20:17:49 +02:00
def _set_fallbacks ( self ) :
for k , v in self . argument_spec . items ( ) :
fallback = v . get ( ' fallback ' , ( None , ) )
fallback_strategy = fallback [ 0 ]
fallback_args = [ ]
fallback_kwargs = { }
if k not in self . params and fallback_strategy is not None :
for item in fallback [ 1 : ] :
if isinstance ( item , dict ) :
fallback_kwargs = item
else :
fallback_args = item
try :
self . params [ k ] = fallback_strategy ( * fallback_args , * * fallback_kwargs )
except AnsibleFallbackNotFound :
continue
2013-10-31 21:52:37 +01:00
def _load_params ( self ) :
2016-05-18 19:50:55 +02:00
''' read the input and set the params attribute.
2016-04-14 18:06:38 +02:00
2016-05-18 19:50:55 +02:00
This method is for backwards compatibility . The guts of the function
were moved out in 2.1 so that custom modules could read the parameters .
'''
# debug overrides to read args from file or cmdline
self . params = _load_params ( )
2016-04-05 20:06:17 +02:00
2015-09-26 05:57:03 +02:00
def _log_to_syslog ( self , msg ) :
2015-10-14 15:12:02 +02:00
if HAS_SYSLOG :
2016-06-10 17:48:54 +02:00
module = ' ansible- %s ' % self . _name
2016-05-13 05:30:05 +02:00
facility = getattr ( syslog , self . _syslog_facility , syslog . LOG_USER )
2016-04-05 20:06:17 +02:00
syslog . openlog ( str ( module ) , 0 , facility )
2015-10-14 15:12:02 +02:00
syslog . syslog ( syslog . LOG_INFO , msg )
2015-09-26 05:57:03 +02:00
2015-10-01 06:09:15 +02:00
def debug ( self , msg ) :
if self . _debug :
self . log ( msg )
2015-09-26 05:57:03 +02:00
def log ( self , msg , log_args = None ) :
if not self . no_log :
if log_args is None :
log_args = dict ( )
2016-06-10 17:48:54 +02:00
module = ' ansible- %s ' % self . _name
2016-08-20 17:08:59 +02:00
if isinstance ( module , binary_type ) :
2015-10-02 20:11:48 +02:00
module = module . decode ( ' utf-8 ' , ' replace ' )
2015-09-26 05:57:03 +02:00
# 6655 - allow for accented characters
2016-08-20 17:08:59 +02:00
if not isinstance ( msg , ( binary_type , text_type ) ) :
2015-10-02 20:11:48 +02:00
raise TypeError ( " msg should be a string (got %s ) " % type ( msg ) )
# We want journal to always take text type
# syslog takes bytes on py2, text type on py3
2016-08-20 17:08:59 +02:00
if isinstance ( msg , binary_type ) :
2015-10-21 03:51:34 +02:00
journal_msg = remove_values ( msg . decode ( ' utf-8 ' , ' replace ' ) , self . no_log_values )
else :
# TODO: surrogateescape is a danger here on Py3
journal_msg = remove_values ( msg , self . no_log_values )
2016-06-05 01:19:57 +02:00
if PY3 :
2015-10-21 03:51:34 +02:00
syslog_msg = journal_msg
2015-10-02 20:11:48 +02:00
else :
2015-10-21 03:51:34 +02:00
syslog_msg = journal_msg . encode ( ' utf-8 ' , ' replace ' )
2015-09-26 05:57:03 +02:00
2015-10-02 20:11:48 +02:00
if has_journal :
2015-09-26 05:57:03 +02:00
journal_args = [ ( " MODULE " , os . path . basename ( __file__ ) ) ]
for arg in log_args :
journal_args . append ( ( arg . upper ( ) , str ( log_args [ arg ] ) ) )
try :
2015-10-02 20:11:48 +02:00
journal . send ( u " %s %s " % ( module , journal_msg ) , * * dict ( journal_args ) )
2015-09-26 05:57:03 +02:00
except IOError :
# fall back to syslog since logging to journal failed
2015-10-02 20:11:48 +02:00
self . _log_to_syslog ( syslog_msg )
2015-09-26 05:57:03 +02:00
else :
2015-10-02 20:11:48 +02:00
self . _log_to_syslog ( syslog_msg )
2013-10-31 21:52:37 +01:00
def _log_invocation ( self ) :
''' log that ansible ran the module '''
# TODO: generalize a separate log function and make log_invocation use it
# Sanitize possible password argument when logging.
log_args = dict ( )
passwd_keys = [ ' password ' , ' login_password ' ]
2014-02-13 21:23:49 +01:00
2013-10-31 21:52:37 +01:00
for param in self . params :
canon = self . aliases . get ( param , param )
arg_opts = self . argument_spec . get ( canon , { } )
no_log = arg_opts . get ( ' no_log ' , False )
2014-09-05 02:57:52 +02:00
2014-08-19 17:46:46 +02:00
if self . boolean ( no_log ) :
2013-10-31 21:52:37 +01:00
log_args [ param ] = ' NOT_LOGGING_PARAMETER '
elif param in passwd_keys :
log_args [ param ] = ' NOT_LOGGING_PASSWORD '
else :
2014-09-05 02:57:52 +02:00
param_val = self . params [ param ]
2016-08-20 17:08:59 +02:00
if not isinstance ( param_val , ( text_type , binary_type ) ) :
2014-09-05 02:57:52 +02:00
param_val = str ( param_val )
2016-08-20 17:08:59 +02:00
elif isinstance ( param_val , text_type ) :
2014-09-05 02:57:52 +02:00
param_val = param_val . encode ( ' utf-8 ' )
2015-10-21 03:51:34 +02:00
log_args [ param ] = heuristic_log_sanitize ( param_val , self . no_log_values )
2013-10-31 21:52:37 +01:00
2014-09-04 20:14:37 +02:00
msg = [ ]
2013-10-31 21:52:37 +01:00
for arg in log_args :
2014-09-04 20:14:37 +02:00
arg_val = log_args [ arg ]
2016-08-20 17:08:59 +02:00
if not isinstance ( arg_val , ( text_type , binary_type ) ) :
2014-09-04 20:14:37 +02:00
arg_val = str ( arg_val )
2016-08-20 17:08:59 +02:00
elif isinstance ( arg_val , text_type ) :
2014-09-04 20:14:37 +02:00
arg_val = arg_val . encode ( ' utf-8 ' )
2016-03-23 16:00:33 +01:00
msg . append ( ' %s = %s ' % ( arg , arg_val ) )
2013-10-31 21:52:37 +01:00
if msg :
2016-03-23 16:00:33 +01:00
msg = ' Invoked with %s ' % ' ' . join ( msg )
2013-10-31 21:52:37 +01:00
else :
msg = ' Invoked '
2015-09-26 05:57:03 +02:00
self . log ( msg , log_args = log_args )
2014-03-25 21:07:05 +01:00
2014-03-18 16:17:44 +01:00
def _set_cwd ( self ) :
try :
cwd = os . getcwd ( )
if not os . access ( cwd , os . F_OK | os . R_OK ) :
raise
return cwd
except :
2016-02-04 22:54:03 +01:00
# we don't have access to the cwd, probably because of sudo.
2014-03-18 16:17:44 +01:00
# Try and move to a neutral location to prevent errors
for cwd in [ os . path . expandvars ( ' $HOME ' ) , tempfile . gettempdir ( ) ] :
try :
if os . access ( cwd , os . F_OK | os . R_OK ) :
os . chdir ( cwd )
return cwd
except :
pass
2016-02-04 22:54:03 +01:00
# we won't error here, as it may *not* be a problem,
2014-03-18 16:17:44 +01:00
# and we don't want to break modules unnecessarily
2016-02-04 22:54:03 +01:00
return None
2014-03-18 16:17:44 +01:00
2013-10-31 21:52:37 +01:00
def get_bin_path ( self , arg , required = False , opt_dirs = [ ] ) :
'''
find system executable in PATH .
Optional arguments :
- required : if executable is not found and required is true , fail_json
- opt_dirs : optional list of directories to search in addition to PATH
if found return full path ; otherwise return None
'''
sbin_paths = [ ' /sbin ' , ' /usr/sbin ' , ' /usr/local/sbin ' ]
paths = [ ]
for d in opt_dirs :
if d is not None and os . path . exists ( d ) :
paths . append ( d )
paths + = os . environ . get ( ' PATH ' , ' ' ) . split ( os . pathsep )
bin_path = None
# mangle PATH to include /sbin dirs
for p in sbin_paths :
if p not in paths and os . path . exists ( p ) :
paths . append ( p )
for d in paths :
2016-03-20 20:55:48 +01:00
if not d :
continue
2013-10-31 21:52:37 +01:00
path = os . path . join ( d , arg )
2015-09-25 16:46:09 +02:00
if os . path . exists ( path ) and is_executable ( path ) :
2013-10-31 21:52:37 +01:00
bin_path = path
break
if required and bin_path is None :
self . fail_json ( msg = ' Failed to find required executable %s ' % arg )
return bin_path
def boolean ( self , arg ) :
''' return a bool for the arg '''
2016-09-01 13:19:03 +02:00
if arg is None or isinstance ( arg , bool ) :
2013-10-31 21:52:37 +01:00
return arg
2016-08-20 17:08:59 +02:00
if isinstance ( arg , string_types ) :
2013-10-31 21:52:37 +01:00
arg = arg . lower ( )
if arg in BOOLEANS_TRUE :
return True
elif arg in BOOLEANS_FALSE :
return False
else :
self . fail_json ( msg = ' Boolean %s not in either boolean list ' % arg )
def jsonify ( self , data ) :
2015-05-04 04:47:26 +02:00
for encoding in ( " utf-8 " , " latin-1 " ) :
2014-02-11 21:19:00 +01:00
try :
return json . dumps ( data , encoding = encoding )
2015-05-04 04:47:26 +02:00
# Old systems using old simplejson module does not support encoding keyword.
except TypeError :
try :
new_data = json_dict_bytes_to_unicode ( data , encoding = encoding )
except UnicodeDecodeError :
continue
return json . dumps ( new_data )
except UnicodeDecodeError :
2014-02-11 21:19:00 +01:00
continue
self . fail_json ( msg = ' Invalid unicode encoding encountered ' )
2013-10-31 21:52:37 +01:00
def from_json ( self , data ) :
return json . loads ( data )
2014-05-13 20:52:38 +02:00
def add_cleanup_file ( self , path ) :
if path not in self . cleanup_files :
self . cleanup_files . append ( path )
def do_cleanup_files ( self ) :
for path in self . cleanup_files :
self . cleanup ( path )
2013-10-31 21:52:37 +01:00
def exit_json ( self , * * kwargs ) :
''' return from the module, without error '''
self . add_path_info ( kwargs )
2013-11-01 00:47:05 +01:00
if not ' changed ' in kwargs :
2013-10-31 21:52:37 +01:00
kwargs [ ' changed ' ] = False
2015-12-19 20:24:59 +01:00
if ' invocation ' not in kwargs :
2015-12-23 04:45:25 +01:00
kwargs [ ' invocation ' ] = { ' module_args ' : self . params }
2015-10-21 03:51:34 +02:00
kwargs = remove_values ( kwargs , self . no_log_values )
2014-05-13 20:52:38 +02:00
self . do_cleanup_files ( )
2016-04-08 17:18:35 +02:00
print ( ' \n %s ' % self . jsonify ( kwargs ) )
2013-10-31 21:52:37 +01:00
sys . exit ( 0 )
def fail_json ( self , * * kwargs ) :
''' return from the module, with an error message '''
self . add_path_info ( kwargs )
assert ' msg ' in kwargs , " implementation error -- msg to explain the error is required "
kwargs [ ' failed ' ] = True
2015-12-19 20:24:59 +01:00
if ' invocation ' not in kwargs :
2015-12-23 04:45:25 +01:00
kwargs [ ' invocation ' ] = { ' module_args ' : self . params }
2015-10-21 03:51:34 +02:00
kwargs = remove_values ( kwargs , self . no_log_values )
2014-05-13 20:52:38 +02:00
self . do_cleanup_files ( )
2016-04-08 17:18:35 +02:00
print ( ' \n %s ' % self . jsonify ( kwargs ) )
2013-10-31 21:52:37 +01:00
sys . exit ( 1 )
2016-02-04 22:54:03 +01:00
def fail_on_missing_params ( self , required_params = None ) :
''' This is for checking for required params when we can not check via argspec because we
need more information than is simply given in the argspec .
'''
if not required_params :
return
missing_params = [ ]
for required_param in required_params :
if not self . params . get ( required_param ) :
missing_params . append ( required_param )
if missing_params :
self . fail_json ( msg = " missing required arguments: %s " % ' , ' . join ( missing_params ) )
2015-08-03 11:00:25 +02:00
def digest_from_file ( self , filename , algorithm ) :
''' Return hex digest of local file for a digest_method specified by name, or None if file is not present. '''
2013-10-31 21:52:37 +01:00
if not os . path . exists ( filename ) :
return None
if os . path . isdir ( filename ) :
self . fail_json ( msg = " attempted to take checksum of directory: %s " % filename )
2015-08-03 11:00:25 +02:00
# preserve old behaviour where the third parameter was a hash algorithm object
if hasattr ( algorithm , ' hexdigest ' ) :
digest_method = algorithm
else :
try :
2015-08-06 23:39:31 +02:00
digest_method = AVAILABLE_HASH_ALGORITHMS [ algorithm ] ( )
2015-08-03 11:00:25 +02:00
except KeyError :
self . fail_json ( msg = " Could not hash file ' %s ' with algorithm ' %s ' . Available algorithms: %s " %
2015-08-06 23:39:31 +02:00
( filename , algorithm , ' , ' . join ( AVAILABLE_HASH_ALGORITHMS ) ) )
2015-08-03 11:00:25 +02:00
2013-10-31 21:52:37 +01:00
blocksize = 64 * 1024
infile = open ( filename , ' rb ' )
block = infile . read ( blocksize )
while block :
2015-08-03 11:00:25 +02:00
digest_method . update ( block )
2013-10-31 21:52:37 +01:00
block = infile . read ( blocksize )
infile . close ( )
2015-08-03 11:00:25 +02:00
return digest_method . hexdigest ( )
2013-10-31 21:52:37 +01:00
def md5 ( self , filename ) :
2014-11-10 21:00:49 +01:00
''' Return MD5 hex digest of local file using digest_from_file().
Do not use this function unless you have no other choice for :
1 ) Optional backwards compatibility
2 ) Compatibility with a third party protocol
This function will not work on systems complying with FIPS - 140 - 2.
Most uses of this function can use the module . sha1 function instead .
'''
2015-08-06 23:39:31 +02:00
if ' md5 ' not in AVAILABLE_HASH_ALGORITHMS :
raise ValueError ( ' MD5 not available. Possibly running in FIPS mode ' )
2015-08-03 11:00:25 +02:00
return self . digest_from_file ( filename , ' md5 ' )
2013-10-31 21:52:37 +01:00
2014-11-07 06:28:04 +01:00
def sha1 ( self , filename ) :
''' Return SHA1 hex digest of local file using digest_from_file(). '''
2015-08-03 11:00:25 +02:00
return self . digest_from_file ( filename , ' sha1 ' )
2014-11-07 06:28:04 +01:00
2013-10-31 21:52:37 +01:00
def sha256 ( self , filename ) :
''' Return SHA-256 hex digest of local file using digest_from_file(). '''
2015-08-03 11:00:25 +02:00
return self . digest_from_file ( filename , ' sha256 ' )
2013-10-31 21:52:37 +01:00
def backup_local ( self , fn ) :
''' make a date-marked backup of the specified file, return True or False on success or failure '''
2015-04-07 05:37:32 +02:00
backupdest = ' '
if os . path . exists ( fn ) :
# backups named basename-YYYY-MM-DD@HH:MM:SS~
ext = time . strftime ( " % Y- % m- %d @ % H: % M: % S~ " , time . localtime ( time . time ( ) ) )
2016-07-30 05:03:34 +02:00
backupdest = ' %s . %s . %s ' % ( fn , os . getpid ( ) , ext )
2015-04-07 05:37:32 +02:00
try :
shutil . copy2 ( fn , backupdest )
2015-09-23 08:43:17 +02:00
except ( shutil . Error , IOError ) :
e = get_exception ( )
2015-04-07 05:37:32 +02:00
self . fail_json ( msg = ' Could not make backup of %s to %s : %s ' % ( fn , backupdest , e ) )
2013-10-31 21:52:37 +01:00
return backupdest
2014-05-13 20:52:38 +02:00
def cleanup ( self , tmpfile ) :
2013-10-31 21:52:37 +01:00
if os . path . exists ( tmpfile ) :
try :
os . unlink ( tmpfile )
2015-09-23 08:43:17 +02:00
except OSError :
e = get_exception ( )
2013-10-31 21:52:37 +01:00
sys . stderr . write ( " could not cleanup %s : %s " % ( tmpfile , e ) )
2016-02-04 03:15:48 +01:00
def atomic_move ( self , src , dest , unsafe_writes = False ) :
2013-10-31 21:52:37 +01:00
''' atomically move src to dest, copying attributes from dest, returns true on success
it uses os . rename to ensure this as it is an atomic operation , rest of the function is
to work around limitations , corner cases and ensure selinux context is saved if possible '''
context = None
2014-03-25 19:00:38 +01:00
dest_stat = None
2016-09-01 13:19:03 +02:00
b_src = to_bytes ( src , errors = ' surrogate_or_strict ' )
b_dest = to_bytes ( dest , errors = ' surrogate_or_strict ' )
2016-08-29 18:11:40 +02:00
if os . path . exists ( b_dest ) :
2013-10-31 21:52:37 +01:00
try :
2016-08-29 18:11:40 +02:00
dest_stat = os . stat ( b_dest )
os . chmod ( b_src , dest_stat . st_mode & PERM_BITS )
os . chown ( b_src , dest_stat . st_uid , dest_stat . st_gid )
2015-09-23 08:43:17 +02:00
except OSError :
e = get_exception ( )
2013-10-31 21:52:37 +01:00
if e . errno != errno . EPERM :
raise
if self . selinux_enabled ( ) :
context = self . selinux_context ( dest )
else :
if self . selinux_enabled ( ) :
context = self . selinux_default_context ( dest )
2016-08-29 18:11:40 +02:00
creating = not os . path . exists ( b_dest )
2014-06-03 16:36:19 +02:00
try :
login_name = os . getlogin ( )
except OSError :
# not having a tty can cause the above to fail, so
# just get the LOGNAME environment variable instead
login_name = os . environ . get ( ' LOGNAME ' , None )
# if the original login_name doesn't match the currently
# logged-in user, or if the SUDO_USER environment variable
# is set, then this user has switched their credentials
switched_user = login_name and login_name != pwd . getpwuid ( os . getuid ( ) ) [ 0 ] or os . environ . get ( ' SUDO_USER ' )
2014-03-24 21:10:43 +01:00
2013-10-31 21:52:37 +01:00
try :
2014-03-14 04:07:35 +01:00
# Optimistically try a rename, solves some corner cases and can avoid useless work, throws exception if not atomic.
2016-08-29 18:11:40 +02:00
os . rename ( b_src , b_dest )
2015-09-23 08:43:17 +02:00
except ( IOError , OSError ) :
e = get_exception ( )
2016-02-15 18:08:07 +01:00
if e . errno not in [ errno . EPERM , errno . EXDEV , errno . EACCES , errno . ETXTBSY ] :
2016-02-04 03:15:48 +01:00
# only try workarounds for errno 18 (cross device), 1 (not permitted), 13 (permission denied)
# and 26 (text file busy) which happens on vagrant synced folders and other 'exotic' non posix file systems
2013-10-31 21:52:37 +01:00
self . fail_json ( msg = ' Could not replace file: %s to %s : %s ' % ( src , dest , e ) )
2016-02-15 10:18:44 +01:00
else :
2016-08-29 18:11:40 +02:00
b_dest_dir = os . path . dirname ( b_dest )
# Converting from bytes so that if py3, it will be
# surrogateescaped. If py2, it wil be a noop. Converting
# from text strings could mangle filenames on py2)
native_dest_dir = to_native ( b_dest_dir )
native_suffix = to_native ( os . path . basename ( b_dest ) )
native_prefix = ' .ansible_tmp '
2014-08-21 21:07:18 +02:00
try :
2016-08-26 16:41:17 +02:00
tmp_dest_fd , tmp_dest_name = tempfile . mkstemp (
2016-08-29 18:11:40 +02:00
prefix = native_prefix , dir = native_dest_dir , suffix = native_suffix )
2016-02-15 10:18:44 +01:00
except ( OSError , IOError ) :
2015-09-23 08:43:17 +02:00
e = get_exception ( )
2016-08-29 18:11:40 +02:00
self . fail_json ( msg = ' The destination directory ( %s ) is not writable by the current user. Error was: %s ' % ( os . path . dirname ( dest ) , e ) )
2016-09-01 13:19:03 +02:00
b_tmp_dest_name = to_bytes ( tmp_dest_name , errors = ' surrogate_or_strict ' )
2016-02-15 10:18:44 +01:00
2016-08-26 16:41:17 +02:00
try :
2016-02-15 10:18:44 +01:00
try :
2016-08-26 16:41:17 +02:00
# close tmp file handle before file operations to prevent text file busy errors on vboxfs synced folders (windows host)
os . close ( tmp_dest_fd )
# leaves tmp file behind when sudo and not root
if switched_user and os . getuid ( ) != 0 :
# cleanup will happen by 'rm' of tempdir
# copy2 will preserve some metadata
2016-08-29 18:11:40 +02:00
shutil . copy2 ( b_src , b_tmp_dest_name )
2016-08-26 16:41:17 +02:00
else :
2016-08-29 18:11:40 +02:00
shutil . move ( b_src , b_tmp_dest_name )
2016-08-26 16:41:17 +02:00
if self . selinux_enabled ( ) :
self . set_context_if_different (
2016-08-29 18:11:40 +02:00
b_tmp_dest_name , context , False )
2016-02-15 18:08:07 +01:00
try :
2016-08-29 18:11:40 +02:00
tmp_stat = os . stat ( b_tmp_dest_name )
2016-08-26 16:41:17 +02:00
if dest_stat and ( tmp_stat . st_uid != dest_stat . st_uid or tmp_stat . st_gid != dest_stat . st_gid ) :
2016-08-29 18:11:40 +02:00
os . chown ( b_tmp_dest_name , dest_stat . st_uid , dest_stat . st_gid )
2016-08-26 16:41:17 +02:00
except OSError :
2016-02-15 18:08:07 +01:00
e = get_exception ( )
2016-08-26 16:41:17 +02:00
if e . errno != errno . EPERM :
raise
2016-08-29 18:11:40 +02:00
os . rename ( b_tmp_dest_name , b_dest )
2016-08-26 16:41:17 +02:00
except ( shutil . Error , OSError , IOError ) :
e = get_exception ( )
# sadly there are some situations where we cannot ensure atomicity, but only if
# the user insists and we get the appropriate error we update the file unsafely
if unsafe_writes and e . errno == errno . EBUSY :
#TODO: issue warning that this is an unsafe operation, but doing it cause user insists
try :
try :
2016-08-29 18:11:40 +02:00
out_dest = open ( b_dest , ' wb ' )
in_src = open ( b_src , ' rb ' )
2016-08-26 16:41:17 +02:00
shutil . copyfileobj ( in_src , out_dest )
finally : # assuring closed files in 2.4 compatible way
if out_dest :
out_dest . close ( )
if in_src :
in_src . close ( )
except ( shutil . Error , OSError , IOError ) :
e = get_exception ( )
self . fail_json ( msg = ' Could not write data to file ( %s ) from ( %s ): %s ' % ( dest , src , e ) )
else :
self . fail_json ( msg = ' Could not replace file: %s to %s : %s ' % ( src , dest , e ) )
finally :
2016-08-29 18:11:40 +02:00
self . cleanup ( b_tmp_dest_name )
2013-10-31 21:52:37 +01:00
2014-04-29 15:40:08 +02:00
if creating :
# make sure the file has the correct permissions
# based on the current value of umask
umask = os . umask ( 0 )
os . umask ( umask )
2016-08-29 18:11:40 +02:00
os . chmod ( b_dest , DEFAULT_PERM & ~ umask )
2014-05-27 23:04:02 +02:00
if switched_user :
2016-08-29 18:11:40 +02:00
os . chown ( b_dest , os . getuid ( ) , os . getgid ( ) )
2014-03-24 21:10:43 +01:00
2013-10-31 21:52:37 +01:00
if self . selinux_enabled ( ) :
# rename might not preserve context
self . set_context_if_different ( dest , context , False )
2016-01-20 18:04:44 +01:00
def run_command ( self , args , check_rc = False , close_fds = True , executable = None , data = None , binary_data = False , path_prefix = None , cwd = None , use_unsafe_shell = False , prompt_regex = None , environ_update = None ) :
2013-10-31 21:52:37 +01:00
'''
Execute a command , returns rc , stdout , and stderr .
2016-01-20 18:04:44 +01:00
: arg args : is the command to run
* If args is a list , the command will be run with shell = False .
* If args is a string and use_unsafe_shell = False it will split args to a list and run with shell = False
* If args is a string and use_unsafe_shell = True it runs with shell = True .
: kw check_rc : Whether to call fail_json in case of non zero RC .
Default False
: kw close_fds : See documentation for subprocess . Popen ( ) . Default True
: kw executable : See documentation for subprocess . Popen ( ) . Default None
: kw data : If given , information to write to the stdin of the command
: kw binary_data : If False , append a newline to the data . Default False
: kw path_prefix : If given , additional path to find the command in .
This adds to the PATH environment vairable so helper commands in
the same directory can also be found
2016-08-27 04:52:12 +02:00
: kw cwd : If given , working directory to run the command inside
2016-01-20 18:04:44 +01:00
: kw use_unsafe_shell : See ` args ` parameter . Default False
: kw prompt_regex : Regex string ( not a compiled regex ) which can be
used to detect prompts in the stdout which would otherwise cause
the execution to hang ( especially if no input data is specified )
: kwarg environ_update : dictionary to * update * os . environ with
2013-10-31 21:52:37 +01:00
'''
2014-03-10 22:11:24 +01:00
shell = False
2013-10-31 21:52:37 +01:00
if isinstance ( args , list ) :
2014-03-13 19:51:10 +01:00
if use_unsafe_shell :
args = " " . join ( [ pipes . quote ( x ) for x in args ] )
shell = True
2016-08-20 17:08:59 +02:00
elif isinstance ( args , ( binary_type , text_type ) ) and use_unsafe_shell :
2013-10-31 21:52:37 +01:00
shell = True
2016-08-26 10:30:46 +02:00
elif isinstance ( args , ( binary_type , text_type ) ) :
2016-06-05 01:19:57 +02:00
# On python2.6 and below, shlex has problems with text type
# On python3, shlex needs a text type.
2016-08-26 10:30:46 +02:00
if PY2 :
2016-09-01 13:19:03 +02:00
args = to_bytes ( args , errors = ' surrogate_or_strict ' )
2016-08-26 10:30:46 +02:00
elif PY3 :
args = to_text ( args , errors = ' surrogateescape ' )
2016-01-18 22:26:54 +01:00
args = shlex . split ( args )
2013-10-31 21:52:37 +01:00
else :
msg = " Argument ' args ' to run_command must be list or string "
self . fail_json ( rc = 257 , cmd = args , msg = msg )
2014-03-10 22:11:24 +01:00
2014-11-11 06:41:50 +01:00
prompt_re = None
if prompt_regex :
2016-06-05 01:19:57 +02:00
if isinstance ( prompt_regex , text_type ) :
if PY3 :
2016-08-26 10:30:46 +02:00
prompt_regex = to_bytes ( prompt_regex , errors = ' surrogateescape ' )
2016-06-05 01:19:57 +02:00
elif PY2 :
2016-09-01 13:19:03 +02:00
prompt_regex = to_bytes ( prompt_regex , errors = ' surrogate_or_strict ' )
2014-11-11 06:41:50 +01:00
try :
prompt_re = re . compile ( prompt_regex , re . MULTILINE )
except re . error :
self . fail_json ( msg = " invalid prompt regular expression given to run_command " )
2014-03-12 15:10:45 +01:00
# expand things like $HOME and ~
2014-03-12 16:57:28 +01:00
if not shell :
2016-08-26 10:30:46 +02:00
args = [ os . path . expanduser ( os . path . expandvars ( x ) ) for x in args if x is not None ]
2014-03-12 15:10:45 +01:00
2013-10-31 21:52:37 +01:00
rc = 0
msg = None
st_in = None
2013-11-07 21:50:41 +01:00
2016-01-20 18:04:44 +01:00
# Manipulate the environ we'll send to the new process
old_env_vals = { }
2016-02-07 21:45:03 +01:00
# We can set this from both an attribute and per call
for key , val in self . run_command_environ_update . items ( ) :
old_env_vals [ key ] = os . environ . get ( key , None )
os . environ [ key ] = val
2016-01-20 18:04:44 +01:00
if environ_update :
for key , val in environ_update . items ( ) :
old_env_vals [ key ] = os . environ . get ( key , None )
os . environ [ key ] = val
2013-11-07 21:50:41 +01:00
if path_prefix :
2016-01-20 18:04:44 +01:00
old_env_vals [ ' PATH ' ] = os . environ [ ' PATH ' ]
os . environ [ ' PATH ' ] = " %s : %s " % ( path_prefix , os . environ [ ' PATH ' ] )
2013-11-07 21:50:41 +01:00
2016-05-02 17:29:35 +02:00
# If using test-module and explode, the remote lib path will resemble ...
# /tmp/test_module_scratch/debug_dir/ansible/module_utils/basic.py
# If using ansible or ansible-playbook with a remote system ...
# /tmp/ansible_vmweLQ/ansible_modlib.zip/ansible/module_utils/basic.py
2016-07-21 19:58:24 +02:00
# Clean out python paths set by ansiballz
2016-05-02 17:29:35 +02:00
if ' PYTHONPATH ' in os . environ :
pypaths = os . environ [ ' PYTHONPATH ' ] . split ( ' : ' )
pypaths = [ x for x in pypaths \
if not x . endswith ( ' /ansible_modlib.zip ' ) \
and not x . endswith ( ' /debug_dir ' ) ]
os . environ [ ' PYTHONPATH ' ] = ' : ' . join ( pypaths )
2016-06-15 16:02:56 +02:00
if not os . environ [ ' PYTHONPATH ' ] :
del os . environ [ ' PYTHONPATH ' ]
2016-05-02 17:29:35 +02:00
2014-03-06 20:33:18 +01:00
# create a printable version of the command for use
# in reporting later, which strips out things like
# passwords from the args list
2016-06-05 01:19:57 +02:00
to_clean_args = args
if PY2 :
if isinstance ( args , text_type ) :
to_clean_args = args . encode ( ' utf-8 ' )
2014-03-06 20:33:18 +01:00
else :
2016-06-05 01:19:57 +02:00
if isinstance ( args , binary_type ) :
to_clean_args = args . decode ( ' utf-8 ' , errors = ' replace ' )
if isinstance ( args , ( text_type , binary_type ) ) :
to_clean_args = shlex . split ( to_clean_args )
2015-02-09 19:13:13 +01:00
clean_args = [ ]
is_passwd = False
for arg in to_clean_args :
if is_passwd :
is_passwd = False
clean_args . append ( ' ******** ' )
continue
if PASSWD_ARG_RE . match ( arg ) :
sep_idx = arg . find ( ' = ' )
if sep_idx > - 1 :
clean_args . append ( ' %s =******** ' % arg [ : sep_idx ] )
continue
else :
is_passwd = True
2015-10-21 03:51:34 +02:00
arg = heuristic_log_sanitize ( arg , self . no_log_values )
clean_args . append ( arg )
2015-02-09 19:13:13 +01:00
clean_args = ' ' . join ( pipes . quote ( arg ) for arg in clean_args )
2014-03-06 20:33:18 +01:00
2013-10-31 21:52:37 +01:00
if data :
st_in = subprocess . PIPE
2014-03-10 22:11:24 +01:00
kwargs = dict (
executable = executable ,
shell = shell ,
close_fds = close_fds ,
2014-08-04 22:32:41 +02:00
stdin = st_in ,
2014-03-10 22:11:24 +01:00
stdout = subprocess . PIPE ,
2016-01-20 18:04:44 +01:00
stderr = subprocess . PIPE ,
2014-03-10 22:11:24 +01:00
)
2014-03-12 21:59:24 +01:00
if cwd and os . path . isdir ( cwd ) :
2014-03-10 22:11:24 +01:00
kwargs [ ' cwd ' ] = cwd
2014-03-13 22:15:23 +01:00
# store the pwd
prev_dir = os . getcwd ( )
2014-03-10 22:11:24 +01:00
2014-03-13 22:15:23 +01:00
# make sure we're in the right working directory
if cwd and os . path . isdir ( cwd ) :
try :
2014-03-12 19:59:50 +01:00
os . chdir ( cwd )
2015-09-23 08:43:17 +02:00
except ( OSError , IOError ) :
e = get_exception ( )
2014-05-03 18:40:05 +02:00
self . fail_json ( rc = e . errno , msg = " Could not open %s , %s " % ( cwd , str ( e ) ) )
2014-03-12 20:33:31 +01:00
2014-03-13 22:15:23 +01:00
try :
2015-09-26 05:57:03 +02:00
2015-10-01 06:09:15 +02:00
if self . _debug :
2015-09-26 05:57:03 +02:00
if isinstance ( args , list ) :
running = ' ' . join ( args )
else :
running = args
self . log ( ' Executing: ' + running )
2014-03-10 22:11:24 +01:00
cmd = subprocess . Popen ( args , * * kwargs )
2014-08-04 22:32:41 +02:00
# the communication logic here is essentially taken from that
# of the _communicate() function in ssh.py
2016-06-05 01:19:57 +02:00
stdout = b ( ' ' )
stderr = b ( ' ' )
2014-08-04 22:32:41 +02:00
rpipes = [ cmd . stdout , cmd . stderr ]
2013-10-31 21:52:37 +01:00
if data :
if not binary_data :
2014-03-13 20:28:51 +01:00
data + = ' \n '
2016-06-05 01:19:57 +02:00
if isinstance ( data , text_type ) :
if PY3 :
errors = ' surrogateescape '
else :
errors = ' strict '
data = data . encode ( ' utf-8 ' , errors = errors )
2014-08-04 22:32:41 +02:00
cmd . stdin . write ( data )
cmd . stdin . close ( )
while True :
rfd , wfd , efd = select . select ( rpipes , [ ] , rpipes , 1 )
if cmd . stdout in rfd :
dat = os . read ( cmd . stdout . fileno ( ) , 9000 )
stdout + = dat
2016-06-05 01:19:57 +02:00
if dat == b ( ' ' ) :
2014-08-04 22:32:41 +02:00
rpipes . remove ( cmd . stdout )
if cmd . stderr in rfd :
dat = os . read ( cmd . stderr . fileno ( ) , 9000 )
stderr + = dat
2016-06-05 01:19:57 +02:00
if dat == b ( ' ' ) :
2014-08-04 22:32:41 +02:00
rpipes . remove ( cmd . stderr )
2014-11-11 06:41:50 +01:00
# if we're checking for prompts, do it now
if prompt_re :
if prompt_re . search ( stdout ) and not data :
2015-06-24 19:22:37 +02:00
return ( 257 , stdout , " A prompt was encountered while running a command, but no input data was specified " )
2014-08-04 22:32:41 +02:00
# only break out if no pipes are left to read or
# the pipes are completely read and
# the process is terminated
if ( not rpipes or not rfd ) and cmd . poll ( ) is not None :
break
# No pipes are left to read but process is not yet terminated
# Only then it is safe to wait for the process to be finished
# NOTE: Actually cmd.poll() is always None here if rpipes is empty
elif not rpipes and cmd . poll ( ) == None :
cmd . wait ( )
# The process is terminated. Since no pipes to read from are
# left, there is no need to call select() again.
break
cmd . stdout . close ( )
cmd . stderr . close ( )
2013-10-31 21:52:37 +01:00
rc = cmd . returncode
2015-09-23 08:43:17 +02:00
except ( OSError , IOError ) :
e = get_exception ( )
2016-09-02 17:27:01 +02:00
self . fail_json ( rc = e . errno , msg = to_native ( e ) , cmd = clean_args )
except Exception :
e = get_exception ( )
self . fail_json ( rc = 257 , msg = to_native ( e ) , exception = traceback . format_exc ( ) , cmd = clean_args )
2014-03-13 21:06:59 +01:00
2016-01-20 18:04:44 +01:00
# Restore env settings
for key , val in old_env_vals . items ( ) :
if val is None :
del os . environ [ key ]
else :
os . environ [ key ] = val
2013-10-31 21:52:37 +01:00
if rc != 0 and check_rc :
2015-10-21 03:51:34 +02:00
msg = heuristic_log_sanitize ( stderr . rstrip ( ) , self . no_log_values )
2014-08-04 22:32:41 +02:00
self . fail_json ( cmd = clean_args , rc = rc , stdout = stdout , stderr = stderr , msg = msg )
2014-03-13 22:15:23 +01:00
# reset the pwd
os . chdir ( prev_dir )
2014-08-04 22:32:41 +02:00
return ( rc , stdout , stderr )
2013-10-31 21:52:37 +01:00
2014-03-12 15:55:54 +01:00
def append_to_file ( self , filename , str ) :
filename = os . path . expandvars ( os . path . expanduser ( filename ) )
fh = open ( filename , ' a ' )
fh . write ( str )
fh . close ( )
2016-08-16 19:45:41 +02:00
def bytes_to_human ( self , size ) :
2016-08-24 21:04:20 +02:00
return bytes_to_human ( size )
2013-10-31 21:52:37 +01:00
2016-08-16 19:45:41 +02:00
# for backwards compatibility
pretty_bytes = bytes_to_human
2016-08-24 21:04:20 +02:00
def human_to_bytes ( self , number , isbits = False ) :
return human_to_bytes ( number , isbits )
2016-08-16 19:45:41 +02:00
2015-09-25 16:46:09 +02:00
#
# Backwards compat
#
# In 2.0, moved from inside the module to the toplevel
is_executable = is_executable
2014-03-19 15:30:10 +01:00
def get_module_path ( ) :
return os . path . dirname ( os . path . realpath ( __file__ ) )