def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect facts = dict() cacheable = boolean(self._task.args.pop('cacheable', False)) if self._task.args: for (k, v) in iteritems(self._task.args): k = self._templar.template(k) if not isidentifier(k): result['failed'] = True result['msg'] = ( "The variable name '%s' is not valid. Variables must start with a letter or underscore character, and contain only " "letters, numbers and underscores." % k) return result if not C.DEFAULT_JINJA2_NATIVE and isinstance( v, string_types) and v.lower() in ('true', 'false', 'yes', 'no'): v = boolean(v, strict=False) facts[k] = v result['changed'] = False result['assible_facts'] = facts result['_assible_facts_cacheable'] = cacheable return result
def test_junk_values_strict(self): with pytest.raises(TypeError): assert boolean("flibbity", strict=True) is False with pytest.raises(TypeError): assert boolean(42, strict=True) is False with pytest.raises(TypeError): assert boolean(42.0, strict=True) is False with pytest.raises(TypeError): assert boolean(object(), strict=True) is False
def do_diff(self, arg): """Toggle whether plays run with diff""" if arg: self.diff = boolean(arg, strict=False) display.v("diff mode changed to %s" % self.diff) else: display.display("Please specify a diff value , e.g. `diff yes`")
def _add_host_to_composed_groups(self, groups, variables, host, strict=False): ''' helper to create complex groups for plugins based on jinja2 conditionals, hosts that meet the conditional are added to group''' # process each 'group entry' if groups and isinstance(groups, dict): variables = combine_vars(variables, self.inventory.get_host(host).get_vars()) self.templar.available_variables = variables for group_name in groups: conditional = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % groups[ group_name] group_name = original_safe(group_name, force=True) try: result = boolean(self.templar.template(conditional)) except Exception as e: if strict: raise AssibleParserError( "Could not add host %s to group %s: %s" % (host, group_name, to_native(e))) continue if result: # ensure group exists, use sanitized name group_name = self.inventory.add_group(group_name) # add host to group self.inventory.add_child(group_name, host)
def do_check(self, arg): """Toggle whether plays run with check mode""" if arg: self.check_mode = boolean(arg, strict=False) display.v("check mode changed to %s" % self.check_mode) else: display.display( "Please specify check mode value, e.g. `check yes`")
def do_become(self, arg): """Toggle whether plays run with become""" if arg: self.become = boolean(arg, strict=False) display.v("become changed to %s" % self.become) self.set_prompt() else: display.display("Please specify become value, e.g. `become yes`")
def test_strings(self): assert boolean("true") is True assert boolean("TRUE") is True assert boolean("t") is True assert boolean("yes") is True assert boolean("y") is True assert boolean("on") is True
def get_validated_value(self, name, attribute, value, templar): if attribute.isa == 'string': value = to_text(value) elif attribute.isa == 'int': value = int(value) elif attribute.isa == 'float': value = float(value) elif attribute.isa == 'bool': value = boolean(value, strict=True) elif attribute.isa == 'percent': # special value, which may be an integer or float # with an optional '%' at the end if isinstance(value, string_types) and '%' in value: value = value.replace('%', '') value = float(value) elif attribute.isa == 'list': if value is None: value = [] elif not isinstance(value, list): value = [value] if attribute.listof is not None: for item in value: if not isinstance(item, attribute.listof): raise AssibleParserError("the field '%s' should be a list of %s, " "but the item '%s' is a %s" % (name, attribute.listof, item, type(item)), obj=self.get_ds()) elif attribute.required and attribute.listof == string_types: if item is None or item.strip() == "": raise AssibleParserError("the field '%s' is required, and cannot have empty values" % (name,), obj=self.get_ds()) elif attribute.isa == 'set': if value is None: value = set() elif not isinstance(value, (list, set)): if isinstance(value, string_types): value = value.split(',') else: # Making a list like this handles strings of # text and bytes properly value = [value] if not isinstance(value, set): value = set(value) elif attribute.isa == 'dict': if value is None: value = dict() elif not isinstance(value, dict): raise TypeError("%s is not a dictionary" % value) elif attribute.isa == 'class': if not isinstance(value, attribute.class_type): raise TypeError("%s is not a valid %s (got a %s instead)" % (name, attribute.class_type, type(value))) value.post_validate(templar=templar) return value
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect stats = {'data': {}, 'per_host': False, 'aggregate': True} if self._task.args: data = self._task.args.get('data', {}) if not isinstance(data, dict): data = self._templar.template(data, convert_bare=False, fail_on_undefined=True) if not isinstance(data, dict): result['failed'] = True result[ 'msg'] = "The 'data' option needs to be a dictionary/hash" return result # set boolean options, defaults are set above in stats init for opt in ['per_host', 'aggregate']: val = self._task.args.get(opt, None) if val is not None: if not isinstance(val, bool): stats[opt] = boolean(self._templar.template(val), strict=False) else: stats[opt] = val for (k, v) in iteritems(data): k = self._templar.template(k) if not isidentifier(k): result['failed'] = True result['msg'] = ( "The variable name '%s' is not valid. Variables must start with a letter or underscore character, and contain only " "letters, numbers and underscores." % k) return result stats['data'][k] = self._templar.template(v) result['changed'] = False result['assible_stats'] = stats return result
def load_provider(spec, args): provider = args.get("provider") or {} for key, value in iteritems(spec): if key not in provider: if "fallback" in value: provider[key] = _fallback(value["fallback"]) elif "default" in value: provider[key] = value["default"] else: provider[key] = None if "authorize" in provider: # Coerce authorize to provider if a string has somehow snuck in. provider["authorize"] = boolean(provider["authorize"] or False) args["provider"] = provider return provider
def load_provider(spec, args): provider = args.get('provider') or {} for key, value in iteritems(spec): if key not in provider: if 'fallback' in value: provider[key] = _fallback(value['fallback']) elif 'default' in value: provider[key] = value['default'] else: provider[key] = None if 'authorize' in provider: # Coerce authorize to provider if a string has somehow snuck in. provider['authorize'] = boolean(provider['authorize'] or False) args['provider'] = provider return provider
def truthy(value, convert_bool=False): """Evaluate as value for truthiness using python ``bool`` Optionally, attempt to do a conversion to bool from boolean like values such as ``"false"``, ``"true"``, ``"yes"``, ``"no"``, ``"on"``, ``"off"``, etc. .. versionadded:: 2.10 """ if convert_bool: try: value = boolean(value) except TypeError: pass return bool(value)
def check_type_bool(value): """Verify that the value is a bool or convert it to a bool and return it. Raises TypeError if unable to convert to a bool :arg value: String, int, or float to convert to bool. Valid booleans include: '1', 'on', 1, '0', 0, 'n', 'f', 'false', 'true', 'y', 't', 'yes', 'no', 'off' :returns: Boolean True or False """ if isinstance(value, bool): return value if isinstance(value, string_types) or isinstance(value, (int, float)): return boolean(value) raise TypeError('%s cannot be converted to a bool' % type(value))
def run(self, terms, variables, **kwargs): def _raise_terms_error(msg=""): raise AssibleError( "subelements lookup expects a list of two or three items, " + msg) terms[0] = listify_lookup_plugin_terms(terms[0], templar=self._templar, loader=self._loader) # check lookup terms - check number of terms if not isinstance(terms, list) or not 2 <= len(terms) <= 3: _raise_terms_error() # first term should be a list (or dict), second a string holding the subkey if not isinstance(terms[0], (list, dict)) or not isinstance( terms[1], string_types): _raise_terms_error( "first a dict or a list, second a string pointing to the subkey" ) subelements = terms[1].split(".") if isinstance(terms[0], dict): # convert to list: if terms[0].get('skipped', False) is not False: # the registered result was completely skipped return [] elementlist = [] for key in terms[0]: elementlist.append(terms[0][key]) else: elementlist = terms[0] # check for optional flags in third term flags = {} if len(terms) == 3: flags = terms[2] if not isinstance(flags, dict) and not all( [isinstance(key, string_types) and key in FLAGS for key in flags]): _raise_terms_error( "the optional third item must be a dict with flags %s" % FLAGS) # build_items ret = [] for item0 in elementlist: if not isinstance(item0, dict): raise AssibleError( "subelements lookup expects a dictionary, got '%s'" % item0) if item0.get('skipped', False) is not False: # this particular item is to be skipped continue skip_missing = boolean(flags.get('skip_missing', False), strict=False) subvalue = item0 lastsubkey = False sublist = [] for subkey in subelements: if subkey == subelements[-1]: lastsubkey = True if subkey not in subvalue: if skip_missing: continue else: raise AssibleError( "could not find '%s' key in iterated item '%s'" % (subkey, subvalue)) if not lastsubkey: if not isinstance(subvalue[subkey], dict): if skip_missing: continue else: raise AssibleError( "the key %s should point to a dictionary, got '%s'" % (subkey, subvalue[subkey])) else: subvalue = subvalue[subkey] else: # lastsubkey if not isinstance(subvalue[subkey], list): raise AssibleError( "the key %s should point to a list, got '%s'" % (subkey, subvalue[subkey])) else: sublist = subvalue.pop(subkey, []) for item1 in sublist: ret.append((item0, item1)) return ret
def test_junk_values_nonstrict(self): assert boolean("flibbity", strict=False) is False assert boolean(42, strict=False) is False assert boolean(42.0, strict=False) is False assert boolean(object(), strict=False) is False
def test_numbers(self): assert boolean(1) is True assert boolean(0) is False assert boolean(0.0) is False
def test_none(self): with pytest.raises(TypeError): assert boolean(None, strict=True) is False assert boolean(None, strict=False) is False
def test_bools(self): assert boolean(True) is True assert boolean(False) is False
def run(self, tmp=None, task_vars=None): ''' handler for file transfer operations ''' if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect source = self._task.args.get('src', None) content = self._task.args.get('content', None) dest = self._task.args.get('dest', None) remote_src = boolean(self._task.args.get('remote_src', False), strict=False) local_follow = boolean(self._task.args.get('local_follow', True), strict=False) result['failed'] = True if not source and content is None: result['msg'] = 'src (or content) is required' elif not dest: result['msg'] = 'dest is required' elif source and content is not None: result['msg'] = 'src and content are mutually exclusive' elif content is not None and dest is not None and dest.endswith("/"): result['msg'] = "can not use content with a dir as dest" else: del result['failed'] if result.get('failed'): return self._ensure_invocation(result) # Define content_tempfile in case we set it after finding content populated. content_tempfile = None # If content is defined make a tmp file and write the content into it. if content is not None: try: # If content comes to us as a dict it should be decoded json. # We need to encode it back into a string to write it out. if isinstance(content, dict) or isinstance(content, list): content_tempfile = self._create_content_tempfile( json.dumps(content)) else: content_tempfile = self._create_content_tempfile(content) source = content_tempfile except Exception as err: result['failed'] = True result[ 'msg'] = "could not write content temp file: %s" % to_native( err) return self._ensure_invocation(result) # if we have first_available_file in our vars # look up the files and use the first one we find as src elif remote_src: result.update( self._execute_module(module_name='assible.legacy.copy', task_vars=task_vars)) return self._ensure_invocation(result) else: # find_needle returns a path that may not have a trailing slash on # a directory so we need to determine that now (we use it just # like rsync does to figure out whether to include the directory # or only the files inside the directory trailing_slash = source.endswith(os.path.sep) try: # find in expected paths source = self._find_needle('files', source) except AssibleError as e: result['failed'] = True result['msg'] = to_text(e) result['exception'] = traceback.format_exc() return self._ensure_invocation(result) if trailing_slash != source.endswith(os.path.sep): if source[-1] == os.path.sep: source = source[:-1] else: source = source + os.path.sep # A list of source file tuples (full_path, relative_path) which will try to copy to the destination source_files = {'files': [], 'directories': [], 'symlinks': []} # If source is a directory populate our list else source is a file and translate it to a tuple. if os.path.isdir(to_bytes(source, errors='surrogate_or_strict')): # Get a list of the files we want to replicate on the remote side source_files = _walk_dirs(source, local_follow=local_follow, trailing_slash_detector=self._connection. _shell.path_has_trailing_slash) # If it's recursive copy, destination is always a dir, # explicitly mark it so (note - copy module relies on this). if not self._connection._shell.path_has_trailing_slash(dest): dest = self._connection._shell.join_path(dest, '') # FIXME: Can we optimize cases where there's only one file, no # symlinks and any number of directories? In the original code, # empty directories are not copied.... else: source_files['files'] = [(source, os.path.basename(source))] changed = False module_return = dict(changed=False) # A register for if we executed a module. # Used to cut down on command calls when not recursive. module_executed = False # expand any user home dir specifier dest = self._remote_expand_user(dest) implicit_directories = set() for source_full, source_rel in source_files['files']: # copy files over. This happens first as directories that have # a file do not need to be created later # We only follow symlinks for files in the non-recursive case if source_files['directories']: follow = False else: follow = boolean(self._task.args.get('follow', False), strict=False) module_return = self._copy_file(source_full, source_rel, content, content_tempfile, dest, task_vars, follow) if module_return is None: continue if module_return.get('failed'): result.update(module_return) return self._ensure_invocation(result) paths = os.path.split(source_rel) dir_path = '' for dir_component in paths: os.path.join(dir_path, dir_component) implicit_directories.add(dir_path) if 'diff' in result and not result['diff']: del result['diff'] module_executed = True changed = changed or module_return.get('changed', False) for src, dest_path in source_files['directories']: # Find directories that are leaves as they might not have been # created yet. if dest_path in implicit_directories: continue # Use file module to create these new_module_args = _create_remote_file_args(self._task.args) new_module_args['path'] = os.path.join(dest, dest_path) new_module_args['state'] = 'directory' new_module_args['mode'] = self._task.args.get( 'directory_mode', None) new_module_args['recurse'] = False del new_module_args['src'] module_return = self._execute_module( module_name='assible.legacy.file', module_args=new_module_args, task_vars=task_vars) if module_return.get('failed'): result.update(module_return) return self._ensure_invocation(result) module_executed = True changed = changed or module_return.get('changed', False) for target_path, dest_path in source_files['symlinks']: # Copy symlinks over new_module_args = _create_remote_file_args(self._task.args) new_module_args['path'] = os.path.join(dest, dest_path) new_module_args['src'] = target_path new_module_args['state'] = 'link' new_module_args['force'] = True # Only follow remote symlinks in the non-recursive case if source_files['directories']: new_module_args['follow'] = False # file module cannot deal with 'preserve' mode and is meaningless # for symlinks anyway, so just don't pass it. if new_module_args.get('mode', None) == 'preserve': new_module_args.pop('mode') module_return = self._execute_module( module_name='assible.legacy.file', module_args=new_module_args, task_vars=task_vars) module_executed = True if module_return.get('failed'): result.update(module_return) return self._ensure_invocation(result) changed = changed or module_return.get('changed', False) if module_executed and len(source_files['files']) == 1: result.update(module_return) # the file module returns the file path as 'path', but # the copy module uses 'dest', so add it if it's not there if 'path' in result and 'dest' not in result: result['dest'] = result['path'] else: result.update(dict(dest=dest, src=source, changed=changed)) # Delete tmp path self._remove_tmp_path(self._connection._shell.tmpdir) return self._ensure_invocation(result)
def run(self, tmp=None, task_vars=None): ''' handler for file transfer operations ''' if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect source = self._task.args.get('src', None) content = self._task.args.get('content', None) dest = self._task.args.get('dest', None) remote_src = boolean(self._task.args.get('remote_src', False), strict=False) local_follow = boolean(self._task.args.get('local_follow', False), strict=False) force = boolean(self._task.args.get('force', True), strict=False) decrypt = boolean(self._task.args.get('decrypt', True), strict=False) backup = boolean(self._task.args.get('backup', False), strict=False) result['src'] = source result['dest'] = dest result['failed'] = True if (source is None and content is None) or dest is None: result['msg'] = "src (or content) and dest are required" elif source is not None and content is not None: result['msg'] = "src and content are mutually exclusive" elif content is not None and dest is not None and (dest.endswith( os.path.sep) or dest.endswith(self.WIN_PATH_SEPARATOR)): result['msg'] = "dest must be a file if content is defined" else: del result['failed'] if result.get('failed'): return result # If content is defined make a temp file and write the content into it content_tempfile = None if content is not None: try: # if content comes to us as a dict it should be decoded json. # We need to encode it back into a string and write it out if isinstance(content, dict) or isinstance(content, list): content_tempfile = self._create_content_tempfile( json.dumps(content)) else: content_tempfile = self._create_content_tempfile(content) source = content_tempfile except Exception as err: result['failed'] = True result[ 'msg'] = "could not write content tmp file: %s" % to_native( err) return result # all actions should occur on the remote server, run win_copy module elif remote_src: new_module_args = self._task.args.copy() new_module_args.update( dict( _copy_mode="remote", dest=dest, src=source, force=force, backup=backup, )) new_module_args.pop('content', None) result.update( self._execute_module(module_args=new_module_args, task_vars=task_vars)) return result # find_needle returns a path that may not have a trailing slash on a # directory so we need to find that out first and append at the end else: trailing_slash = source.endswith(os.path.sep) try: # find in expected paths source = self._find_needle('files', source) except AssibleError as e: result['failed'] = True result['msg'] = to_text(e) result['exception'] = traceback.format_exc() return result if trailing_slash != source.endswith(os.path.sep): if source[-1] == os.path.sep: source = source[:-1] else: source = source + os.path.sep # A list of source file tuples (full_path, relative_path) which will try to copy to the destination source_files = {'files': [], 'directories': [], 'symlinks': []} # If source is a directory populate our list else source is a file and translate it to a tuple. if os.path.isdir(to_bytes(source, errors='surrogate_or_strict')): result['operation'] = 'folder_copy' # Get a list of the files we want to replicate on the remote side source_files = _walk_dirs(source, self._loader, decrypt=decrypt, local_follow=local_follow, trailing_slash_detector=self._connection. _shell.path_has_trailing_slash, checksum_check=force) # If it's recursive copy, destination is always a dir, # explicitly mark it so (note - win_copy module relies on this). if not self._connection._shell.path_has_trailing_slash(dest): dest = "%s%s" % (dest, self.WIN_PATH_SEPARATOR) check_dest = dest # Source is a file, add details to source_files dict else: result['operation'] = 'file_copy' # If the local file does not exist, get_real_file() raises AssibleFileNotFound try: source_full = self._loader.get_real_file(source, decrypt=decrypt) except AssibleFileNotFound as e: result['failed'] = True result['msg'] = "could not find src=%s, %s" % (source_full, to_text(e)) return result original_basename = os.path.basename(source) result['original_basename'] = original_basename # check if dest ends with / or \ and append source filename to dest if self._connection._shell.path_has_trailing_slash(dest): check_dest = dest filename = original_basename result['dest'] = self._connection._shell.join_path( dest, filename) else: # replace \\ with / so we can use os.path to get the filename or dirname unix_path = dest.replace(self.WIN_PATH_SEPARATOR, os.path.sep) filename = os.path.basename(unix_path) check_dest = os.path.dirname(unix_path) file_checksum = _get_local_checksum(force, source_full) source_files['files'].append( dict(src=source_full, dest=filename, checksum=file_checksum)) result['checksum'] = file_checksum result['size'] = os.path.getsize( to_bytes(source_full, errors='surrogate_or_strict')) # find out the files/directories/symlinks that we need to copy to the server query_args = self._task.args.copy() query_args.update( dict( _copy_mode="query", dest=check_dest, force=force, files=source_files['files'], directories=source_files['directories'], symlinks=source_files['symlinks'], )) # src is not required for query, will fail path validation is src has unix allowed chars query_args.pop('src', None) query_args.pop('content', None) query_return = self._execute_module(module_args=query_args, task_vars=task_vars) if query_return.get('failed') is True: result.update(query_return) return result if len(query_return['files']) > 0 or len(query_return[ 'directories']) > 0 and self._connection._shell.tmpdir is None: self._connection._shell.tmpdir = self._make_tmp_path() if len(query_return['files']) == 1 and len( query_return['directories']) == 0: # we only need to copy 1 file, don't mess around with zips file_src = query_return['files'][0]['src'] file_dest = query_return['files'][0]['dest'] result.update( self._copy_single_file(file_src, dest, file_dest, task_vars, self._connection._shell.tmpdir, backup)) if result.get('failed') is True: result['msg'] = "failed to copy file %s: %s" % (file_src, result['msg']) result['changed'] = True elif len(query_return['files']) > 0 or len( query_return['directories']) > 0: # either multiple files or directories need to be copied, compress # to a zip and 'explode' the zip on the server # TODO: handle symlinks result.update( self._copy_zip_file(dest, source_files['files'], source_files['directories'], task_vars, self._connection._shell.tmpdir, backup)) result['changed'] = True else: # no operations need to occur result['failed'] = False result['changed'] = False # remove the content tmp file and remote tmp file if it was created self._remove_tempfile_if_content_defined(content, content_tempfile) self._remove_tmp_path(self._connection._shell.tmpdir) return result
def run(self, tmp=None, task_vars=None): ''' handler for fetch operations ''' if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect try: if self._play_context.check_mode: raise AssibleActionSkip( 'check mode not (yet) supported for this module') source = self._task.args.get('src', None) original_dest = dest = self._task.args.get('dest', None) flat = boolean(self._task.args.get('flat'), strict=False) fail_on_missing = boolean(self._task.args.get( 'fail_on_missing', True), strict=False) validate_checksum = boolean(self._task.args.get( 'validate_checksum', True), strict=False) msg = '' # validate source and dest are strings FIXME: use basic.py and module specs if not isinstance(source, string_types): msg = "Invalid type supplied for source option, it must be a string" if not isinstance(dest, string_types): msg = "Invalid type supplied for dest option, it must be a string" if source is None or dest is None: msg = "src and dest are required" if msg: raise AssibleActionFail(msg) source = self._connection._shell.join_path(source) source = self._remote_expand_user(source) remote_checksum = None if not self._connection.become: # calculate checksum for the remote file, don't bother if using become as slurp will be used # Force remote_checksum to follow symlinks because fetch always follows symlinks remote_checksum = self._remote_checksum(source, all_vars=task_vars, follow=True) # use slurp if permissions are lacking or privilege escalation is needed remote_data = None if remote_checksum in ('1', '2', None): slurpres = self._execute_module( module_name='assible.legacy.slurp', module_args=dict(src=source), task_vars=task_vars) if slurpres.get('failed'): if not fail_on_missing and ( slurpres.get('msg').startswith('file not found') or remote_checksum == '1'): result[ 'msg'] = "the remote file does not exist, not transferring, ignored" result['file'] = source result['changed'] = False else: result.update(slurpres) return result else: if slurpres['encoding'] == 'base64': remote_data = base64.b64decode(slurpres['content']) if remote_data is not None: remote_checksum = checksum_s(remote_data) # calculate the destination name if os.path.sep not in self._connection._shell.join_path('a', ''): source = self._connection._shell._unquote(source) source_local = source.replace('\\', '/') else: source_local = source # ensure we only use file name, avoid relative paths if not is_subpath(dest, original_dest): # TODO: ? dest = os.path.expanduser(dest.replace(('../',''))) raise AssibleActionFail( "Detected directory traversal, expected to be contained in '%s' but got '%s'" % (original_dest, dest)) if flat: if os.path.isdir(to_bytes(dest, errors='surrogate_or_strict') ) and not dest.endswith(os.sep): raise AssibleActionFail( "dest is an existing directory, use a trailing slash if you want to fetch src into that directory" ) if dest.endswith(os.sep): # if the path ends with "/", we'll use the source filename as the # destination filename base = os.path.basename(source_local) dest = os.path.join(dest, base) if not dest.startswith("/"): # if dest does not start with "/", we'll assume a relative path dest = self._loader.path_dwim(dest) else: # files are saved in dest dir, with a subdir for each host, then the filename if 'inventory_hostname' in task_vars: target_name = task_vars['inventory_hostname'] else: target_name = self._play_context.remote_addr dest = "%s/%s/%s" % (self._loader.path_dwim(dest), target_name, source_local) if remote_checksum in ('0', '1', '2', '3', '4', '5'): result['changed'] = False result['file'] = source if remote_checksum == '0': result[ 'msg'] = "unable to calculate the checksum of the remote file" elif remote_checksum == '1': result['msg'] = "the remote file does not exist" elif remote_checksum == '2': result['msg'] = "no read permission on remote file" elif remote_checksum == '3': result[ 'msg'] = "remote file is a directory, fetch cannot work on directories" elif remote_checksum == '4': result[ 'msg'] = "python isn't present on the system. Unable to compute checksum" elif remote_checksum == '5': result[ 'msg'] = "stdlib json was not found on the remote machine. Only the raw module can work without those installed" # Historically, these don't fail because you may want to transfer # a log file that possibly MAY exist but keep going to fetch other # log files. Today, this is better achieved by adding # ignore_errors or failed_when to the task. Control the behaviour # via fail_when_missing if fail_on_missing: result['failed'] = True del result['changed'] else: result['msg'] += ", not transferring, ignored" return result dest = os.path.normpath(dest) # calculate checksum for the local file local_checksum = checksum(dest) if remote_checksum != local_checksum: # create the containing directories, if needed makedirs_safe(os.path.dirname(dest)) # fetch the file and check for changes if remote_data is None: self._connection.fetch_file(source, dest) else: try: f = open(to_bytes(dest, errors='surrogate_or_strict'), 'wb') f.write(remote_data) f.close() except (IOError, OSError) as e: raise AssibleActionFail( "Failed to fetch the file: %s" % e) new_checksum = secure_hash(dest) # For backwards compatibility. We'll return None on FIPS enabled systems try: new_md5 = md5(dest) except ValueError: new_md5 = None if validate_checksum and new_checksum != remote_checksum: result.update( dict(failed=True, md5sum=new_md5, msg="checksum mismatch", file=source, dest=dest, remote_md5sum=None, checksum=new_checksum, remote_checksum=remote_checksum)) else: result.update({ 'changed': True, 'md5sum': new_md5, 'dest': dest, 'remote_md5sum': None, 'checksum': new_checksum, 'remote_checksum': remote_checksum }) else: # For backwards compatibility. We'll return None on FIPS enabled systems try: local_md5 = md5(dest) except ValueError: local_md5 = None result.update( dict(changed=False, md5sum=local_md5, file=source, dest=dest, checksum=local_checksum)) finally: self._remove_tmp_path(self._connection._shell.tmpdir) return result
def run(self, tmp=None, task_vars=None): ''' run the pause action module ''' if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect duration_unit = 'minutes' prompt = None seconds = None echo = True echo_prompt = '' result.update( dict(changed=False, rc=0, stderr='', stdout='', start=None, stop=None, delta=None, echo=echo)) # Should keystrokes be echoed to stdout? if 'echo' in self._task.args: try: echo = boolean(self._task.args['echo']) except TypeError as e: result['failed'] = True result['msg'] = to_native(e) return result # Add a note saying the output is hidden if echo is disabled if not echo: echo_prompt = ' (output is hidden)' # Is 'prompt' a key in 'args'? if 'prompt' in self._task.args: prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), self._task.args['prompt'], echo_prompt) else: # If no custom prompt is specified, set a default prompt prompt = "[%s]\n%s%s:" % (self._task.get_name().strip( ), 'Press enter to continue, Ctrl+C to interrupt', echo_prompt) # Are 'minutes' or 'seconds' keys that exist in 'args'? if 'minutes' in self._task.args or 'seconds' in self._task.args: try: if 'minutes' in self._task.args: # The time() command operates in seconds so we need to # recalculate for minutes=X values. seconds = int(self._task.args['minutes']) * 60 else: seconds = int(self._task.args['seconds']) duration_unit = 'seconds' except ValueError as e: result['failed'] = True result[ 'msg'] = u"non-integer value given for prompt duration:\n%s" % to_text( e) return result ######################################################################## # Begin the hard work! start = time.time() result['start'] = to_text(datetime.datetime.now()) result['user_input'] = b'' stdin_fd = None old_settings = None try: if seconds is not None: if seconds < 1: seconds = 1 # setup the alarm handler signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(seconds) # show the timer and control prompts display.display("Pausing for %d seconds%s" % (seconds, echo_prompt)) display.display( "(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)\r" ), # show the prompt specified in the task if 'prompt' in self._task.args: display.display(prompt) else: display.display(prompt) # save the attributes on the existing (duped) stdin so # that we can restore them later after we set raw mode stdin_fd = None stdout_fd = None try: if PY3: stdin = self._connection._new_stdin.buffer stdout = sys.stdout.buffer else: stdin = self._connection._new_stdin stdout = sys.stdout stdin_fd = stdin.fileno() stdout_fd = stdout.fileno() except (ValueError, AttributeError): # ValueError: someone is using a closed file descriptor as stdin # AttributeError: someone is using a null file descriptor as stdin on windoez stdin = None if stdin_fd is not None: if isatty(stdin_fd): # grab actual Ctrl+C sequence try: intr = termios.tcgetattr(stdin_fd)[6][termios.VINTR] except Exception: # unsupported/not present, use default intr = b'\x03' # value for Ctrl+C # get backspace sequences try: backspace = termios.tcgetattr(stdin_fd)[6][ termios.VERASE] except Exception: backspace = [b'\x7f', b'\x08'] old_settings = termios.tcgetattr(stdin_fd) tty.setraw(stdin_fd) # Only set stdout to raw mode if it is a TTY. This is needed when redirecting # stdout to a file since a file cannot be set to raw mode. if isatty(stdout_fd): tty.setraw(stdout_fd) # Only echo input if no timeout is specified if not seconds and echo: new_settings = termios.tcgetattr(stdin_fd) new_settings[3] = new_settings[3] | termios.ECHO termios.tcsetattr(stdin_fd, termios.TCSANOW, new_settings) # flush the buffer to make sure no previous key presses # are read in below termios.tcflush(stdin, termios.TCIFLUSH) while True: try: if stdin_fd is not None: key_pressed = stdin.read(1) if key_pressed == intr: # value for Ctrl+C clear_line(stdout) raise KeyboardInterrupt if not seconds: if stdin_fd is None or not isatty(stdin_fd): display.warning( "Not waiting for response to prompt as stdin is not interactive" ) break # read key presses and act accordingly if key_pressed in (b'\r', b'\n'): clear_line(stdout) break elif key_pressed in backspace: # delete a character if backspace is pressed result['user_input'] = result['user_input'][:-1] clear_line(stdout) if echo: stdout.write(result['user_input']) stdout.flush() else: result['user_input'] += key_pressed except KeyboardInterrupt: signal.alarm(0) display.display( "Press 'C' to continue the play or 'A' to abort \r"), if self._c_or_a(stdin): clear_line(stdout) break clear_line(stdout) raise AssibleError('user requested abort!') except AssibleTimeoutExceeded: # this is the exception we expect when the alarm signal # fires, so we simply ignore it to move into the cleanup pass finally: # cleanup and save some information # restore the old settings for the duped stdin stdin_fd if not (None in (stdin_fd, old_settings)) and isatty(stdin_fd): termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_settings) duration = time.time() - start result['stop'] = to_text(datetime.datetime.now()) result['delta'] = int(duration) if duration_unit == 'minutes': duration = round(duration / 60.0, 2) else: duration = round(duration, 2) result['stdout'] = "Paused for %s %s" % (duration, duration_unit) result['user_input'] = to_text(result['user_input'], errors='surrogate_or_strict') return result
def run(self, tmp=None, task_vars=None): self._supports_check_mode = False result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect if task_vars is None: task_vars = dict() src = self._task.args.get('src', None) dest = self._task.args.get('dest', None) delimiter = self._task.args.get('delimiter', None) remote_src = self._task.args.get('remote_src', 'yes') regexp = self._task.args.get('regexp', None) follow = self._task.args.get('follow', False) ignore_hidden = self._task.args.get('ignore_hidden', False) decrypt = self._task.args.pop('decrypt', True) try: if src is None or dest is None: raise AssibleActionFail("src and dest are required") if boolean(remote_src, strict=False): # call assemble via assible.legacy to allow library/ overrides of the module without collection search result.update( self._execute_module(module_name='assible.legacy.assemble', task_vars=task_vars)) raise _AssibleActionDone() else: try: src = self._find_needle('files', src) except AssibleError as e: raise AssibleActionFail(to_native(e)) if not os.path.isdir(src): raise AssibleActionFail(u"Source (%s) is not a directory" % src) _re = None if regexp is not None: _re = re.compile(regexp) # Does all work assembling the file path = self._assemble_from_fragments(src, delimiter, _re, ignore_hidden, decrypt) path_checksum = checksum_s(path) dest = self._remote_expand_user(dest) dest_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=follow) diff = {} # setup args for running modules new_module_args = self._task.args.copy() # clean assemble specific options for opt in [ 'remote_src', 'regexp', 'delimiter', 'ignore_hidden', 'decrypt' ]: if opt in new_module_args: del new_module_args[opt] new_module_args['dest'] = dest if path_checksum != dest_stat['checksum']: if self._play_context.diff: diff = self._get_diff_data(dest, path, task_vars) remote_path = self._connection._shell.join_path( self._connection._shell.tmpdir, 'src') xfered = self._transfer_file(path, remote_path) # fix file permissions when the copy is done as a different user self._fixup_perms2( (self._connection._shell.tmpdir, remote_path)) new_module_args.update(dict(src=xfered, )) res = self._execute_module(module_name='assible.legacy.copy', module_args=new_module_args, task_vars=task_vars) if diff: res['diff'] = diff result.update(res) else: result.update( self._execute_module(module_name='assible.legacy.file', module_args=new_module_args, task_vars=task_vars)) except AssibleAction as e: result.update(e.result) finally: self._remove_tmp_path(self._connection._shell.tmpdir) return result
def _copy_file(self, source_full, source_rel, content, content_tempfile, dest, task_vars, follow): decrypt = boolean(self._task.args.get('decrypt', True), strict=False) force = boolean(self._task.args.get('force', 'yes'), strict=False) raw = boolean(self._task.args.get('raw', 'no'), strict=False) result = {} result['diff'] = [] # If the local file does not exist, get_real_file() raises AssibleFileNotFound try: source_full = self._loader.get_real_file(source_full, decrypt=decrypt) except AssibleFileNotFound as e: result['failed'] = True result['msg'] = "could not find src=%s, %s" % (source_full, to_text(e)) return result # Get the local mode and set if user wanted it preserved # https://github.com/assible/assible-modules-core/issues/1124 lmode = None if self._task.args.get('mode', None) == 'preserve': lmode = '0%03o' % stat.S_IMODE(os.stat(source_full).st_mode) # This is kind of optimization - if user told us destination is # dir, do path manipulation right away, otherwise we still check # for dest being a dir via remote call below. if self._connection._shell.path_has_trailing_slash(dest): dest_file = self._connection._shell.join_path(dest, source_rel) else: dest_file = dest # Attempt to get remote file info dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow, checksum=force) if dest_status['exists'] and dest_status['isdir']: # The dest is a directory. if content is not None: # If source was defined as content remove the temporary file and fail out. self._remove_tempfile_if_content_defined( content, content_tempfile) result['failed'] = True result['msg'] = "can not use content with a dir as dest" return result else: # Append the relative source location to the destination and get remote stats again dest_file = self._connection._shell.join_path(dest, source_rel) dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow, checksum=force) if dest_status['exists'] and not force: # remote_file exists so continue to next iteration. return None # Generate a hash of the local file. local_checksum = checksum(source_full) if local_checksum != dest_status['checksum']: # The checksums don't match and we will change or error out. if self._play_context.diff and not raw: result['diff'].append( self._get_diff_data(dest_file, source_full, task_vars)) if self._play_context.check_mode: self._remove_tempfile_if_content_defined( content, content_tempfile) result['changed'] = True return result # Define a remote directory that we will copy the file to. tmp_src = self._connection._shell.join_path( self._connection._shell.tmpdir, 'source') remote_path = None if not raw: remote_path = self._transfer_file(source_full, tmp_src) else: self._transfer_file(source_full, dest_file) # We have copied the file remotely and no longer require our content_tempfile self._remove_tempfile_if_content_defined(content, content_tempfile) self._loader.cleanup_tmp_file(source_full) # FIXME: I don't think this is needed when PIPELINING=0 because the source is created # world readable. Access to the directory itself is controlled via fixup_perms2() as # part of executing the module. Check that umask with scp/sftp/piped doesn't cause # a problem before acting on this idea. (This idea would save a round-trip) # fix file permissions when the copy is done as a different user if remote_path: self._fixup_perms2( (self._connection._shell.tmpdir, remote_path)) if raw: # Continue to next iteration if raw is defined. return None # Run the copy module # src and dest here come after original and override them # we pass dest only to make sure it includes trailing slash in case of recursive copy new_module_args = _create_remote_copy_args(self._task.args) new_module_args.update( dict(src=tmp_src, dest=dest, _original_basename=source_rel, follow=follow)) if not self._task.args.get('checksum'): new_module_args['checksum'] = local_checksum if lmode: new_module_args['mode'] = lmode module_return = self._execute_module( module_name='assible.legacy.copy', module_args=new_module_args, task_vars=task_vars) else: # no need to transfer the file, already correct hash, but still need to call # the file module in case we want to change attributes self._remove_tempfile_if_content_defined(content, content_tempfile) self._loader.cleanup_tmp_file(source_full) if raw: return None # Fix for https://github.com/assible/assible-modules-core/issues/1568. # If checksums match, and follow = True, find out if 'dest' is a link. If so, # change it to point to the source of the link. if follow: dest_status_nofollow = self._execute_remote_stat( dest_file, all_vars=task_vars, follow=False) if dest_status_nofollow[ 'islnk'] and 'lnk_source' in dest_status_nofollow.keys( ): dest = dest_status_nofollow['lnk_source'] # Build temporary module_args. new_module_args = _create_remote_file_args(self._task.args) new_module_args.update( dict( dest=dest, _original_basename=source_rel, recurse=False, state='file', )) # src is sent to the file module in _original_basename, not in src try: del new_module_args['src'] except KeyError: pass if lmode: new_module_args['mode'] = lmode # Execute the file module. module_return = self._execute_module( module_name='assible.legacy.file', module_args=new_module_args, task_vars=task_vars) if not module_return.get('checksum'): module_return['checksum'] = local_checksum result.update(module_return) return result
def _build_kwargs(self): self._psrp_host = self.get_option('remote_addr') self._psrp_user = self.get_option('remote_user') self._psrp_pass = self.get_option('remote_password') protocol = self.get_option('protocol') port = self.get_option('port') if protocol is None and port is None: protocol = 'https' port = 5986 elif protocol is None: protocol = 'https' if int(port) != 5985 else 'http' elif port is None: port = 5986 if protocol == 'https' else 5985 self._psrp_protocol = protocol self._psrp_port = int(port) self._psrp_path = self.get_option('path') self._psrp_auth = self.get_option('auth') # cert validation can either be a bool or a path to the cert cert_validation = self.get_option('cert_validation') cert_trust_path = self.get_option('ca_cert') if cert_validation == 'ignore': self._psrp_cert_validation = False elif cert_trust_path is not None: self._psrp_cert_validation = cert_trust_path else: self._psrp_cert_validation = True self._psrp_connection_timeout = self.get_option( 'connection_timeout') # Can be None self._psrp_read_timeout = self.get_option( 'read_timeout') # Can be None self._psrp_message_encryption = self.get_option('message_encryption') self._psrp_proxy = self.get_option('proxy') self._psrp_ignore_proxy = boolean(self.get_option('ignore_proxy')) self._psrp_operation_timeout = int( self.get_option('operation_timeout')) self._psrp_max_envelope_size = int( self.get_option('max_envelope_size')) self._psrp_configuration_name = self.get_option('configuration_name') self._psrp_reconnection_retries = int( self.get_option('reconnection_retries')) self._psrp_reconnection_backoff = float( self.get_option('reconnection_backoff')) self._psrp_certificate_key_pem = self.get_option('certificate_key_pem') self._psrp_certificate_pem = self.get_option('certificate_pem') self._psrp_credssp_auth_mechanism = self.get_option( 'credssp_auth_mechanism') self._psrp_credssp_disable_tlsv1_2 = self.get_option( 'credssp_disable_tlsv1_2') self._psrp_credssp_minimum_version = self.get_option( 'credssp_minimum_version') self._psrp_negotiate_send_cbt = self.get_option('negotiate_send_cbt') self._psrp_negotiate_delegate = self.get_option('negotiate_delegate') self._psrp_negotiate_hostname_override = self.get_option( 'negotiate_hostname_override') self._psrp_negotiate_service = self.get_option('negotiate_service') supported_args = [] for auth_kwarg in AUTH_KWARGS.values(): supported_args.extend(auth_kwarg) extra_args = set([ v.replace('assible_psrp_', '') for v in self.get_option('_extras') ]) unsupported_args = extra_args.difference(supported_args) for arg in unsupported_args: display.warning("assible_psrp_%s is unsupported by the current " "psrp version installed" % arg) self._psrp_conn_kwargs = dict( server=self._psrp_host, port=self._psrp_port, username=self._psrp_user, password=self._psrp_pass, ssl=self._psrp_protocol == 'https', path=self._psrp_path, auth=self._psrp_auth, cert_validation=self._psrp_cert_validation, connection_timeout=self._psrp_connection_timeout, encryption=self._psrp_message_encryption, proxy=self._psrp_proxy, no_proxy=self._psrp_ignore_proxy, max_envelope_size=self._psrp_max_envelope_size, operation_timeout=self._psrp_operation_timeout, certificate_key_pem=self._psrp_certificate_key_pem, certificate_pem=self._psrp_certificate_pem, credssp_auth_mechanism=self._psrp_credssp_auth_mechanism, credssp_disable_tlsv1_2=self._psrp_credssp_disable_tlsv1_2, credssp_minimum_version=self._psrp_credssp_minimum_version, negotiate_send_cbt=self._psrp_negotiate_send_cbt, negotiate_delegate=self._psrp_negotiate_delegate, negotiate_hostname_override=self._psrp_negotiate_hostname_override, negotiate_service=self._psrp_negotiate_service, ) # Check if PSRP version supports newer read_timeout argument (needs pypsrp 0.3.0+) if hasattr(pypsrp, 'FEATURES') and 'wsman_read_timeout' in pypsrp.FEATURES: self._psrp_conn_kwargs['read_timeout'] = self._psrp_read_timeout elif self._psrp_read_timeout is not None: display.warning( "assible_psrp_read_timeout is unsupported by the current psrp version installed, " "using assible_psrp_connection_timeout value for read_timeout instead." ) # Check if PSRP version supports newer reconnection_retries argument (needs pypsrp 0.3.0+) if hasattr(pypsrp, 'FEATURES') and 'wsman_reconnections' in pypsrp.FEATURES: self._psrp_conn_kwargs[ 'reconnection_retries'] = self._psrp_reconnection_retries self._psrp_conn_kwargs[ 'reconnection_backoff'] = self._psrp_reconnection_backoff else: if self._psrp_reconnection_retries is not None: display.warning( "assible_psrp_reconnection_retries is unsupported by the current psrp version installed." ) if self._psrp_reconnection_backoff is not None: display.warning( "assible_psrp_reconnection_backoff is unsupported by the current psrp version installed." ) # add in the extra args that were set for arg in extra_args.intersection(supported_args): option = self.get_option('_extras')['assible_psrp_%s' % arg] self._psrp_conn_kwargs[arg] = option
def run(self, terms, variables, **kwargs): anydict = False skip = False for term in terms: if isinstance(term, dict): anydict = True total_search = [] if anydict: for term in terms: if isinstance(term, dict): files = term.get('files', []) paths = term.get('paths', []) skip = boolean(term.get('skip', False), strict=False) filelist = files if isinstance(files, string_types): files = files.replace(',', ' ') files = files.replace(';', ' ') filelist = files.split(' ') pathlist = paths if paths: if isinstance(paths, string_types): paths = paths.replace(',', ' ') paths = paths.replace(':', ' ') paths = paths.replace(';', ' ') pathlist = paths.split(' ') if not pathlist: total_search = filelist else: for path in pathlist: for fn in filelist: f = os.path.join(path, fn) total_search.append(f) else: total_search.append(term) else: total_search = self._flatten(terms) for fn in total_search: try: fn = self._templar.template(fn) except (AssibleUndefinedVariable, UndefinedError): continue # get subdir if set by task executor, default to files otherwise subdir = getattr(self, '_subdir', 'files') path = None path = self.find_file_in_search_path(variables, subdir, fn, ignore_missing=True) if path is not None: return [path] if skip: return [] raise AssibleLookupError( "No file was found when using first_found. Use errors='ignore' to allow this task to be skipped if no " "files are found")
def run(self, tmp=None, task_vars=None): ''' handler for unarchive operations ''' if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect source = self._task.args.get('src', None) dest = self._task.args.get('dest', None) remote_src = boolean(self._task.args.get('remote_src', False), strict=False) creates = self._task.args.get('creates', None) decrypt = self._task.args.get('decrypt', True) try: # "copy" is deprecated in favor of "remote_src". if 'copy' in self._task.args: # They are mutually exclusive. if 'remote_src' in self._task.args: raise AssibleActionFail( "parameters are mutually exclusive: ('copy', 'remote_src')" ) # We will take the information from copy and store it in # the remote_src var to use later in this file. self._task.args['remote_src'] = remote_src = not boolean( self._task.args.pop('copy'), strict=False) if source is None or dest is None: raise AssibleActionFail( "src (or content) and dest are required") if creates: # do not run the command if the line contains creates=filename # and the filename already exists. This allows idempotence # of command executions. creates = self._remote_expand_user(creates) if self._remote_file_exists(creates): raise AssibleActionSkip("skipped, since %s exists" % creates) dest = self._remote_expand_user( dest) # CCTODO: Fix path for Windows hosts. source = os.path.expanduser(source) if not remote_src: try: source = self._loader.get_real_file(self._find_needle( 'files', source), decrypt=decrypt) except AssibleError as e: raise AssibleActionFail(to_text(e)) try: remote_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=True) except AssibleError as e: raise AssibleActionFail(to_text(e)) if not remote_stat['exists'] or not remote_stat['isdir']: raise AssibleActionFail("dest '%s' must be an existing dir" % dest) if not remote_src: # transfer the file to a remote tmp location tmp_src = self._connection._shell.join_path( self._connection._shell.tmpdir, 'source') self._transfer_file(source, tmp_src) # handle diff mode client side # handle check mode client side # remove action plugin only keys new_module_args = self._task.args.copy() for key in ('decrypt', ): if key in new_module_args: del new_module_args[key] if not remote_src: # fix file permissions when the copy is done as a different user self._fixup_perms2((self._connection._shell.tmpdir, tmp_src)) new_module_args['src'] = tmp_src # execute the unarchive module now, with the updated args (using assible.legacy prefix to eliminate collections # collisions with local override result.update( self._execute_module(module_name='assible.legacy.unarchive', module_args=new_module_args, task_vars=task_vars)) except AssibleAction as e: result.update(e.result) finally: self._remove_tmp_path(self._connection._shell.tmpdir) return result
def ensure_type(value, value_type, origin=None): ''' return a configuration variable with casting :arg value: The value to ensure correct typing of :kwarg value_type: The type of the value. This can be any of the following strings: :boolean: sets the value to a True or False value :bool: Same as 'boolean' :integer: Sets the value to an integer or raises a ValueType error :int: Same as 'integer' :float: Sets the value to a float or raises a ValueType error :list: Treats the value as a comma separated list. Split the value and return it as a python list. :none: Sets the value to None :path: Expands any environment variables and tilde's in the value. :tmppath: Create a unique temporary directory inside of the directory specified by value and return its path. :temppath: Same as 'tmppath' :tmp: Same as 'tmppath' :pathlist: Treat the value as a typical PATH string. (On POSIX, this means colon separated strings.) Split the value and then expand each part for environment variables and tildes. :pathspec: Treat the value as a PATH string. Expands any environment variables tildes's in the value. :str: Sets the value to string types. :string: Same as 'str' ''' errmsg = '' basedir = None if origin and os.path.isabs(origin) and os.path.exists(to_bytes(origin)): basedir = origin if value_type: value_type = value_type.lower() if value is not None: if value_type in ('boolean', 'bool'): value = boolean(value, strict=False) elif value_type in ('integer', 'int'): value = int(value) elif value_type == 'float': value = float(value) elif value_type == 'list': if isinstance(value, string_types): value = [x.strip() for x in value.split(',')] elif not isinstance(value, Sequence): errmsg = 'list' elif value_type == 'none': if value == "None": value = None if value is not None: errmsg = 'None' elif value_type == 'path': if isinstance(value, string_types): value = resolve_path(value, basedir=basedir) else: errmsg = 'path' elif value_type in ('tmp', 'temppath', 'tmppath'): if isinstance(value, string_types): value = resolve_path(value, basedir=basedir) if not os.path.exists(value): makedirs_safe(value, 0o700) prefix = 'assible-local-%s' % os.getpid() value = tempfile.mkdtemp(prefix=prefix, dir=value) atexit.register(cleanup_tmp_file, value, warn=True) else: errmsg = 'temppath' elif value_type == 'pathspec': if isinstance(value, string_types): value = value.split(os.pathsep) if isinstance(value, Sequence): value = [resolve_path(x, basedir=basedir) for x in value] else: errmsg = 'pathspec' elif value_type == 'pathlist': if isinstance(value, string_types): value = [x.strip() for x in value.split(',')] if isinstance(value, Sequence): value = [resolve_path(x, basedir=basedir) for x in value] else: errmsg = 'pathlist' elif value_type in ('dict', 'dictionary'): if not isinstance(value, Mapping): errmsg = 'dictionary' elif value_type in ('str', 'string'): if isinstance(value, (string_types, AssibleVaultEncryptedUnicode, bool, int, float, complex)): value = unquote(to_text(value, errors='surrogate_or_strict')) else: errmsg = 'string' # defaults to string type elif isinstance(value, (string_types, AssibleVaultEncryptedUnicode)): value = unquote(to_text(value, errors='surrogate_or_strict')) if errmsg: raise ValueError('Invalid type provided for "%s": %s' % (errmsg, to_native(value))) return to_text(value, errors='surrogate_or_strict', nonstring='passthru')
def run(self): ''' Run the given playbook, based on the settings in the play which may limit the runs to serialized groups, etc. ''' result = 0 entrylist = [] entry = {} try: # preload become/connection/shell to set config defs cached list(connection_loader.all(class_only=True)) list(shell_loader.all(class_only=True)) list(become_loader.all(class_only=True)) for playbook_path in self._playbooks: pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader) # FIXME: move out of inventory self._inventory.set_playbook_basedir(os.path.realpath(os.path.dirname(playbook_path))) if self._tqm is None: # we are doing a listing entry = {'playbook': playbook_path} entry['plays'] = [] else: # make sure the tqm has callbacks loaded self._tqm.load_callbacks() self._tqm.send_callback('v2_playbook_on_start', pb) i = 1 plays = pb.get_plays() display.vv(u'%d plays in %s' % (len(plays), to_text(playbook_path))) for play in plays: if play._included_path is not None: self._loader.set_basedir(play._included_path) else: self._loader.set_basedir(pb._basedir) # clear any filters which may have been applied to the inventory self._inventory.remove_restriction() # Allow variables to be used in vars_prompt fields. all_vars = self._variable_manager.get_vars(play=play) templar = Templar(loader=self._loader, variables=all_vars) setattr(play, 'vars_prompt', templar.template(play.vars_prompt)) # FIXME: this should be a play 'sub object' like loop_control if play.vars_prompt: for var in play.vars_prompt: vname = var['name'] prompt = var.get("prompt", vname) default = var.get("default", None) private = boolean(var.get("private", True)) confirm = boolean(var.get("confirm", False)) encrypt = var.get("encrypt", None) salt_size = var.get("salt_size", None) salt = var.get("salt", None) unsafe = var.get("unsafe", None) if vname not in self._variable_manager.extra_vars: if self._tqm: self._tqm.send_callback( 'v2_playbook_on_vars_prompt', vname, private, prompt, encrypt, confirm, salt_size, salt, default, unsafe) play.vars[vname] = display.do_var_prompt( vname, private, prompt, encrypt, confirm, salt_size, salt, default, unsafe) else: # we are either in --list-<option> or syntax check play.vars[vname] = default # Post validate so any play level variables are templated all_vars = self._variable_manager.get_vars(play=play) templar = Templar(loader=self._loader, variables=all_vars) play.post_validate(templar) if context.CLIARGS['syntax']: continue if self._tqm is None: # we are just doing a listing entry['plays'].append(play) else: self._tqm._unreachable_hosts.update( self._unreachable_hosts) previously_failed = len(self._tqm._failed_hosts) previously_unreachable = len( self._tqm._unreachable_hosts) break_play = False # we are actually running plays batches = self._get_serialized_batches(play) if len(batches) == 0: self._tqm.send_callback( 'v2_playbook_on_play_start', play) self._tqm.send_callback( 'v2_playbook_on_no_hosts_matched') for batch in batches: # restrict the inventory to the hosts in the serialized batch self._inventory.restrict_to_hosts(batch) # and run it... result = self._tqm.run(play=play) # break the play if the result equals the special return code if result & self._tqm.RUN_FAILED_BREAK_PLAY != 0: result = self._tqm.RUN_FAILED_HOSTS break_play = True # check the number of failures here, to see if they're above the maximum # failure percentage allowed, or if any errors are fatal. If either of those # conditions are met, we break out, otherwise we only break out if the entire # batch failed failed_hosts_count = len(self._tqm._failed_hosts) + len(self._tqm._unreachable_hosts) - \ (previously_failed + previously_unreachable) if len(batch) == failed_hosts_count: break_play = True break # update the previous counts so they don't accumulate incorrectly # over multiple serial batches previously_failed += len( self._tqm._failed_hosts) - previously_failed previously_unreachable += len( self._tqm._unreachable_hosts ) - previously_unreachable # save the unreachable hosts from this batch self._unreachable_hosts.update( self._tqm._unreachable_hosts) if break_play: break i = i + 1 # per play if entry: entrylist.append(entry) # per playbook # send the stats callback for this playbook if self._tqm is not None: if C.RETRY_FILES_ENABLED: retries = set(self._tqm._failed_hosts.keys()) retries.update(self._tqm._unreachable_hosts.keys()) retries = sorted(retries) if len(retries) > 0: if C.RETRY_FILES_SAVE_PATH: basedir = C.RETRY_FILES_SAVE_PATH elif playbook_path: basedir = os.path.dirname( os.path.abspath(playbook_path)) else: basedir = '~/' (retry_name, _) = os.path.splitext( os.path.basename(playbook_path)) filename = os.path.join(basedir, "%s.retry" % retry_name) if self._generate_retry_inventory( filename, retries): display.display( "\tto retry, use: --limit @%s\n" % filename) self._tqm.send_callback('v2_playbook_on_stats', self._tqm._stats) # if the last result wasn't zero, break out of the playbook file name loop if result != 0: break if entrylist: return entrylist finally: if self._tqm is not None: self._tqm.cleanup() if self._loader: self._loader.cleanup_all_tmp_files() if context.CLIARGS['syntax']: display.display("No issues encountered") return result if context.CLIARGS['start_at_task'] and not self._tqm._start_at_done: display.error( "No matching task \"%s\" found." " Note: --start-at-task can only follow static includes." % context.CLIARGS['start_at_task']) return result
def _kerb_auth(self, principal, password): if password is None: password = "" self._kerb_ccache = tempfile.NamedTemporaryFile() display.vvvvv("creating Kerberos CC at %s" % self._kerb_ccache.name) krb5ccname = "FILE:%s" % self._kerb_ccache.name os.environ["KRB5CCNAME"] = krb5ccname krb5env = dict(KRB5CCNAME=krb5ccname) # Stores various flags to call with kinit, these could be explicit args set by 'assible_winrm_kinit_args' OR # '-f' if kerberos delegation is requested (assible_winrm_kerberos_delegation). kinit_cmdline = [self._kinit_cmd] kinit_args = self.get_option('kinit_args') if kinit_args: kinit_args = [ to_text(a) for a in shlex.split(kinit_args) if a.strip() ] kinit_cmdline.extend(kinit_args) elif boolean( self.get_option('_extras').get( 'assible_winrm_kerberos_delegation', False)): kinit_cmdline.append('-f') kinit_cmdline.append(principal) # pexpect runs the process in its own pty so it can correctly send # the password as input even on MacOS which blocks subprocess from # doing so. Unfortunately it is not available on the built in Python # so we can only use it if someone has installed it if HAS_PEXPECT: proc_mechanism = "pexpect" command = kinit_cmdline.pop(0) password = to_text(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with pexpect for principal %s" % principal) try: child = pexpect.spawn(command, kinit_cmdline, timeout=60, env=krb5env, echo=False) except pexpect.ExceptionPexpect as err: err_msg = "Kerberos auth failure when calling kinit cmd " \ "'%s': %s" % (command, to_native(err)) raise AssibleConnectionFailure(err_msg) try: child.expect(".*:") child.sendline(password) except OSError as err: # child exited before the pass was sent, Assible will raise # error based on the rc below, just display the error here display.vvvv("kinit with pexpect raised OSError: %s" % to_native(err)) # technically this is the stdout + stderr but to match the # subprocess error checking behaviour, we will call it stderr stderr = child.read() child.wait() rc = child.exitstatus else: proc_mechanism = "subprocess" password = to_bytes(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with subprocess for principal %s" % principal) try: p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=krb5env) except OSError as err: err_msg = "Kerberos auth failure when calling kinit cmd " \ "'%s': %s" % (self._kinit_cmd, to_native(err)) raise AssibleConnectionFailure(err_msg) stdout, stderr = p.communicate(password + b'\n') rc = p.returncode != 0 if rc != 0: # one last attempt at making sure the password does not exist # in the output exp_msg = to_native(stderr.strip()) exp_msg = exp_msg.replace(to_native(password), "<redacted>") err_msg = "Kerberos auth failure for principal %s with %s: %s" \ % (principal, proc_mechanism, exp_msg) raise AssibleConnectionFailure(err_msg) display.vvvvv("kinit succeeded for principal %s" % principal)