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)) 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 (AnsibleUndefinedVariable, 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] else: if skip: return [] else: raise AnsibleLookupError("No file was found when using with_first_found. Use the 'skip: true' option to allow this task to be skipped if no files are found")
def run(self, tmp=None, task_vars=None): ''' handler for java_cert operations ''' if task_vars is None: task_vars = dict() if not tmp: tmp = self._make_tmp_path() result = super(ActionModule, self).run(tmp, task_vars) cert_path = self._task.args.get('cert_path', None) remote_src = boolean(self._task.args.get('remote_src', True)) # remove remote_src from module argument new_module_args = self._task.args.copy() if 'remote_src' in new_module_args: del new_module_args['remote_src'] if not remote_src: # transfer the file to a remote tmp location tmp_src = self._connection._shell.join_path(tmp, cert_path) self._transfer_file(cert_path, tmp_src) # fix file permissions when copy is done as a different user self._fixup_perms2((tmp, tmp_src)) # Update cert_path value. new_module_args.update(dict(cert_path=tmp_src)) # execute the java_cert module now, with the updated args result.update( self._execute_module(module_args=new_module_args, task_vars=task_vars)) # clean up tmp self._remove_tmp_path(tmp) return result
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) facts = dict() 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 isinstance(v, string_types) and v.lower() in ('true', 'false', 'yes', 'no'): v = boolean(v) facts[k] = v result['changed'] = False result['ansible_facts'] = facts return result
def _set_args(self): """ Set instance variables based on the arguments that were passed """ self.return_results_as_name = self._task.args.get('name', None) self.source_dir = self._task.args.get('dir', None) self.source_file = self._task.args.get('file', None) if not self.source_dir and not self.source_file: self.source_file = self._task.args.get('_raw_params') if self.source_file: self.source_file = self.source_file.rstrip('\n') self.depth = self._task.args.get('depth', None) self.files_matching = self._task.args.get('files_matching', None) self.ignore_unknown_extensions = self._task.args.get( 'ignore_unknown_extensions', False) self.ignore_files = self._task.args.get('ignore_files', None) self.overwrite = boolean(self._task.args.get('overwrite', 'yes')) self.valid_extensions = self._task.args.get('extensions', self.VALID_FILE_EXTENSIONS) # convert/validate extensions list if isinstance(self.valid_extensions, string_types): self.valid_extensions = list(self.valid_extensions) if not isinstance(self.valid_extensions, list): raise AnsibleError( 'Invalid type for "extensions" option, it must be a list')
def boolean_or_cacert(self, validate_certs, cacert): validate_certs = boolean(validate_certs) '''' return a bool or cacert ''' if validate_certs is True: if cacert != '': return cacert else: return True else: return False
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) 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)) 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['ansible_stats'] = stats return result
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) src = self._task.args.get('src', None) remote_src = boolean(self._task.args.get('remote_src', 'no')) remote_user = task_vars.get( 'ansible_ssh_user') or self._play_context.remote_user if src is None: result['failed'] = True result['msg'] = "src is required" return result elif remote_src: # everything is remote, so we just execute the module # without changing any of the module arguments result.update(self._execute_module(task_vars=task_vars)) return result try: src = self._find_needle('files', src) except AnsibleError as e: result['failed'] = True result['msg'] = to_native(e) return result # create the remote tmp dir if needed, and put the source file there if tmp is None or "-tmp-" not in tmp: tmp = self._make_tmp_path(remote_user) self._cleanup_remote_tmp = True tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src)) self._transfer_file(src, tmp_src) self._fixup_perms2((tmp, tmp_src), remote_user) new_module_args = self._task.args.copy() new_module_args.update(dict(src=tmp_src, )) result.update( self._execute_module('patch', module_args=new_module_args, task_vars=task_vars)) self._remove_tmp_path(tmp) return result
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) src = self._task.args.get('src', None) remote_src = boolean(self._task.args.get('remote_src', 'no')) remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if src is None: result['failed'] = True result['msg'] = "src is required" return result elif remote_src: # everything is remote, so we just execute the module # without changing any of the module arguments result.update(self._execute_module(task_vars=task_vars)) return result try: src = self._find_needle('files', src) except AnsibleError as e: result['failed'] = True result['msg'] = to_native(e) return result # create the remote tmp dir if needed, and put the source file there if tmp is None or "-tmp-" not in tmp: tmp = self._make_tmp_path(remote_user) self._cleanup_remote_tmp = True tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src)) self._transfer_file(src, tmp_src) self._fixup_perms2((tmp, tmp_src), remote_user) new_module_args = self._task.args.copy() new_module_args.update( dict( src=tmp_src, ) ) result.update(self._execute_module('patch', module_args=new_module_args, task_vars=task_vars)) self._remove_tmp_path(tmp) return result
def _set_args(self): """ Set instance variables based on the arguments that were passed """ self.VALID_DIR_ARGUMENTS = [ 'dir', 'depth', 'files_matching', 'ignore_files', 'extensions', ] self.VALID_FILE_ARGUMENTS = ['file', '_raw_params'] self.GLOBAL_FILE_ARGUMENTS = ['name', 'overwrite'] self.VALID_ARGUMENTS = (self.VALID_DIR_ARGUMENTS + self.VALID_FILE_ARGUMENTS + self.GLOBAL_FILE_ARGUMENTS) for arg in self._task.args: if arg not in self.VALID_ARGUMENTS: err_msg = '{0} is not a valid option in debug'.format(arg) raise AnsibleError(err_msg) self.return_results_as_name = self._task.args.get('name', None) self.source_dir = self._task.args.get('dir', None) self.source_file = self._task.args.get('file', None) if not self.source_dir and not self.source_file: self.source_file = self._task.args.get('_raw_params') self.depth = self._task.args.get('depth', None) self.files_matching = self._task.args.get('files_matching', None) self.ignore_files = self._task.args.get('ignore_files', None) self.overwrite = boolean(self._task.args.get('overwrite', 'yes')) self.valid_extensions = self._task.args.get('extensions', self.VALID_FILE_EXTENSIONS) if isinstance(self.valid_extensions, string_types): self.valid_extensions = list(self.valid_extensions) # validate if not isinstance(self.valid_extensions, list): raise AnsibleError( 'Invalid type for "extensions" option, it must be a list') self._mutually_exclusive()
def post_validate(self, templar): ''' we can't tell that everything is of the right type until we have all the variables. Run basic types (from isa) as well as any _post_validate_<foo> functions. ''' # save the omit value for later checking omit_value = templar._available_variables.get('omit') for (name, attribute) in iteritems(self._valid_attrs): if getattr(self, name) is None: if not attribute.required: continue else: raise AnsibleParserError( "the field '%s' is required but was not set" % name) elif not attribute.always_post_validate and self.__class__.__name__ not in ( 'Task', 'Handler', 'PlayContext'): # Intermediate objects like Play() won't have their fields validated by # default, as their values are often inherited by other objects and validated # later, so we don't want them to fail out early continue try: # Run the post-validator if present. These methods are responsible for # using the given templar to template the values, if required. method = getattr(self, '_post_validate_%s' % name, None) if method: value = method(attribute, getattr(self, name), templar) elif attribute.isa == 'class': value = getattr(self, name) else: # if the attribute contains a variable, template it now value = templar.template(getattr(self, name)) # if this evaluated to the omit value, set the value back to # the default specified in the FieldAttribute and move on if omit_value is not None and value == omit_value: setattr(self, name, attribute.default) continue # and make sure the attribute is of the type it should be if value is not None: 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) 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 in ('list', 'barelist'): if value is None: value = [] elif not isinstance(value, list): if isinstance(value, string_types ) and attribute.isa == 'barelist': display.deprecated( "Using comma separated values for a list has been deprecated. " "You should instead use the correct YAML syntax for lists. " ) value = value.split(',') else: value = [value] if attribute.listof is not None: for item in value: if not isinstance(item, attribute.listof): raise AnsibleParserError( "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 AnsibleParserError( "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) # and assign the massaged value back to the attribute field setattr(self, name, value) except (TypeError, ValueError) as e: raise AnsibleParserError( "the field '%s' has an invalid value (%s), and could not be converted to an %s." " Error was: %s" % (name, value, attribute.isa, e), obj=self.get_ds()) except (AnsibleUndefinedVariable, UndefinedError) as e: if templar._fail_on_undefined_errors and name != 'name': raise AnsibleParserError( "the field '%s' has an invalid value, which appears to include a variable that is undefined." " The error was: %s" % (name, e), obj=self.get_ds()) self._finalized = True
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) if result.get('skipped'): return result source = self._task.args.get('src', None) content = self._task.args.get('content', None) dest = self._task.args.get('dest', None) raw = boolean(self._task.args.get('raw', 'no')) force = boolean(self._task.args.get('force', 'yes')) remote_src = boolean(self._task.args.get('remote_src', False)) follow = boolean(self._task.args.get('follow', False)) decrypt = boolean(self._task.args.get('decrypt', True)) 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("/"): result['msg'] = "dest must be a file if content is defined" else: del result['failed'] if result.get('failed'): return result # Check if the source ends with a "/" source_trailing_slash = False if source: source_trailing_slash = self._connection._shell.path_has_trailing_slash( source) # Define content_tempfile in case we set it after finding content populated. content_tempfile = None # If content is defined make a temp 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 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(task_vars=task_vars)) return result else: # find in expected paths try: source = self._find_needle('files', source) except AnsibleError as e: result['failed'] = True result['msg'] = to_text(e) return result # A list of source file tuples (full_path, relative_path) which will try to copy to the destination source_files = [] # 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 the amount of spaces to remove to get the relative path. if source_trailing_slash: sz = len(source) else: sz = len(source.rsplit('/', 1)[0]) + 1 # Walk the directory and append the file tuples to source_files. for base_path, sub_folders, files in os.walk(to_bytes(source), followlinks=True): for file in files: full_path = to_text(os.path.join(base_path, file), errors='surrogate_or_strict') rel_path = full_path[sz:] if rel_path.startswith('/'): rel_path = rel_path[1:] source_files.append((full_path, rel_path)) # 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, '') else: source_files.append((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 # Tell _execute_module to delete the file if there is one file. delete_remote_tmp = (len(source_files) == 1) # If this is a recursive action create a tmp path that we can share as the _exec_module create is too late. if not delete_remote_tmp: if tmp is None or "-tmp-" not in tmp: tmp = self._make_tmp_path() # expand any user home dir specifier dest = self._remote_expand_user(dest) # Keep original value for mode parameter mode_value = self._task.args.get('mode', None) diffs = [] for source_full, source_rel in source_files: # If the local file does not exist, get_real_file() raises AnsibleFileNotFound try: source_full = self._loader.get_real_file(source_full, decrypt=decrypt) except AnsibleFileNotFound as e: result['failed'] = True result['msg'] = "could not find src=%s, %s" % (source_full, e) self._remove_tmp_path(tmp) return result # Get the local mode and set if user wanted it preserved # https://github.com/ansible/ansible-modules-core/issues/1124 if self._task.args.get('mode', None) == 'preserve': lmode = '0%03o' % stat.S_IMODE(os.stat(source_full).st_mode) self._task.args['mode'] = lmode # 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 = self._connection._shell.join_path(dest) # Attempt to get remote file info dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow, tmp=tmp, 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) self._remove_tmp_path(tmp) 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, tmp=tmp, checksum=force) if dest_status['exists'] and not force: # remote_file exists so continue to next iteration. continue # 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. changed = True # Create a tmp path if missing only if this is not recursive. # If this is recursive we already have a tmp path. if delete_remote_tmp: if tmp is None or "-tmp-" not in tmp: tmp = self._make_tmp_path() if self._play_context.diff and not raw: diffs.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) changed = True module_return = dict(changed=True) continue # Define a remote directory that we will copy the file to. tmp_src = self._connection._shell.join_path(tmp, '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) # fix file permissions when the copy is done as a different user if remote_path: self._fixup_perms2((tmp, remote_path)) if raw: # Continue to next iteration if raw is defined. continue # 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 = self._task.args.copy() new_module_args.update( dict( src=tmp_src, dest=dest, original_basename=source_rel, )) # remove action plugin only keys for key in ('content', 'decrypt'): if key in new_module_args: del new_module_args[key] module_return = self._execute_module( module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=delete_remote_tmp) module_executed = True 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: # Continue to next iteration if raw is defined. self._remove_tmp_path(tmp) continue # Build temporary module_args. new_module_args = self._task.args.copy() new_module_args.update( dict(src=source_rel, dest=dest, original_basename=source_rel)) # Execute the file module. module_return = self._execute_module( module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=delete_remote_tmp) module_executed = True if not module_return.get('checksum'): module_return['checksum'] = local_checksum if module_return.get('failed'): result.update(module_return) if not delete_remote_tmp: self._remove_tmp_path(tmp) return result if module_return.get('changed'): changed = True # 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 module_return and 'dest' not in module_return: module_return['dest'] = module_return['path'] # reset the mode self._task.args['mode'] = mode_value # Delete tmp path if we were recursive or if we did not execute a module. if not delete_remote_tmp or (delete_remote_tmp and not module_executed): self._remove_tmp_path(tmp) if module_executed and len(source_files) == 1: result.update(module_return) else: result.update(dict(dest=dest, src=source, changed=changed)) if diffs: result['diff'] = diffs return result
def run(self, tmp=None, task_vars=None): ''' handler for file transfer operations ''' # import pydevd; pydevd.settrace('192.168.77.1', port=9999, stdoutToServer=True, stderrToServer=True) if task_vars is None: task_vars = dict() # 执行父类的run方法 result = super(ActionModule, self).run(tmp, task_vars) if result.get('skipped'): return result # 获取参数 source = self._task.args.get('src', None) dest = self._task.args.get('dest', None) force = boolean(self._task.args.get('force', 'yes')) remote_src = boolean(self._task.args.get('remote_src', False)) # 判定参数 result['failed'] = True if source is None or dest is None: result['msg'] = "src and dest are required" elif source is not None and source.endswith("/"): result['msg'] = "src must be a file" else: del result['failed'] if result.get('failed'): return result # 如果copy动作在远端执行,直接返回 if remote_src: result.update(self._execute_module(task_vars=task_vars)) return result # 找到source的路径地址 try: source = self._find_needle('files', source) except AnsibleError as e: result['failed'] = True result['msg'] = to_text(e) return result # 判断是否是目录,如果是跳出返回 if os.path.isdir(to_bytes(source, errors='surrogate_or_strict')): result['failed'] = True result['msg'] = 'src must be a file' return result changed = False module_return = dict(changed=False) # 创建临时目录 if tmp is None or "-tmp-" not in tmp: tmp = self._make_tmp_path() # 5. 获取本地文件,不存在抛出异常 try: source_full = self._loader.get_real_file(source) source_rel = os.path.basename(source) except AnsibleFileNotFound as e: result['failed'] = True result['msg'] = "could not find src=%s, %s" % (source_full, e) self._remove_tmp_path(tmp) return result # 获取远程文件信息 if self._connection._shell.path_has_trailing_slash(dest): dest_file = self._connection._shell.join_path(dest, source_rel) else: dest_file = self._connection._shell.join_path(dest) dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=False, tmp=tmp, checksum=force) # 如果是目录,则返回 if dest_status['exists'] and dest_status['isdir']: self._remove_tmp_path(tmp) result['failed'] = True result['msg'] = "can not use content with a dir as dest" return result # 如果存在,但force为false。则返回 if dest_status['exists'] and not force: return result # 定义拷贝到远程的文件路径 tmp_src = self._connection._shell.join_path(tmp, 'source') # 传送文件 remote_path = None remote_path = self._transfer_file(source_full, tmp_src) # 确保我们的文件具有执行权限 if remote_path: self._fixup_perms2((tmp, remote_path)) # 运行remote_copy 模块 new_module_args = self._task.args.copy() new_module_args.update( dict( src=tmp_src, dest=dest, original_basename=source_rel, )) module_return = self._execute_module(module_name='le_copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp) # 判断运行结果 if module_return.get('failed'): result.update(module_return) return result if module_return.get('changed'): changed = True if module_return: result.update(module_return) else: result.update(dict(dest=dest, src=source, changed=changed)) # 清理临时文件 self._remove_tmp_path(tmp) # 返回结果 return result
def run(self, terms, variables, **kwargs): def _raise_terms_error(msg=""): raise AnsibleError( "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 AnsibleError("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)) subvalue = item0 lastsubkey = False sublist = [] for subkey in subelements: if subkey == subelements[-1]: lastsubkey = True if not subkey in subvalue: if skip_missing: continue else: raise AnsibleError("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 AnsibleError("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 AnsibleError("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 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) if self._play_context.check_mode: result['skipped'] = True result['msg'] = 'check mode not (yet) supported for this module' return result source = self._task.args.get('src', None) dest = self._task.args.get('dest', None) flat = boolean(self._task.args.get('flat')) fail_on_missing = boolean(self._task.args.get('fail_on_missing')) validate_checksum = boolean( self._task.args.get('validate_checksum', self._task.args.get('validate_md5', True))) # validate source and dest are strings FIXME: use basic.py and module specs if not isinstance(source, string_types): result[ 'msg'] = "Invalid type supplied for source option, it must be a string" if not isinstance(dest, string_types): result[ 'msg'] = "Invalid type supplied for dest option, it must be a string" # validate_md5 is the deprecated way to specify validate_checksum if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args: result[ 'msg'] = "validate_checksum and validate_md5 cannot both be specified" if 'validate_md5' in self._task.args: display.deprecated('Use validate_checksum instead of validate_md5', version='2.8') if source is None or dest is None: result['msg'] = "src and dest are required" if result.get('msg'): result['failed'] = True return result source = self._connection._shell.join_path(source) source = self._remote_expand_user(source) remote_checksum = None if not self._play_context.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='slurp', module_args=dict(src=source), task_vars=task_vars, tmp=tmp) 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) # the source path may have been expanded on the # target system, so we compare it here and use the # expanded version if it's different remote_source = slurpres.get('source') if remote_source and remote_source != source: source = remote_source # 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 dest = os.path.expanduser(dest) if flat: 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) dest = dest.replace("//", "/") 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 or simplejson 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 # 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 AnsibleError("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)) return result
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) source = self._task.args.get('src', None) dest = self._task.args.get('dest', None) remote_src = boolean(self._task.args.get('remote_src', False)) creates = self._task.args.get('creates', None) decrypt = self._task.args.get('decrypt', True) # "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: result['failed'] = True result['msg'] = "parameters are mutually exclusive: ('copy', 'remote_src')" return result # 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')) if source is None or dest is None: result['failed'] = True result['msg'] = "src (or content) and dest are required" return result if not tmp: tmp = self._make_tmp_path() if creates: # do not run the command if the line contains creates=filename # and the filename already exists. This allows idempotence # of command executions. if self._remote_file_exists(creates): result['skipped'] = True result['msg'] = "skipped, since %s exists" % creates self._remove_tmp_path(tmp) return result 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 AnsibleError: result['failed'] = True result['msg'] = to_native(get_exception()) self._remove_tmp_path(tmp) return result try: remote_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=True) except AnsibleError: result['failed'] = True result['msg'] = to_native(get_exception()) self._remove_tmp_path(tmp) return result if not remote_stat['exists'] or not remote_stat['isdir']: result['failed'] = True result['msg'] = "dest '%s' must be an existing dir" % dest self._remove_tmp_path(tmp) return result if not remote_src: # transfer the file to a remote tmp location tmp_src = self._connection._shell.join_path(tmp, 'source') self._transfer_file(source, tmp_src) # handle diff mode client side # handle check mode client side if not remote_src: # fix file permissions when the copy is done as a different user self._fixup_perms2((tmp, tmp_src)) # Build temporary module_args. new_module_args = self._task.args.copy() new_module_args.update( dict( src=tmp_src, original_basename=os.path.basename(source), ), ) else: new_module_args = self._task.args.copy() new_module_args.update( dict( original_basename=os.path.basename(source), ), ) # remove action plugin only key for key in ('decrypt',): if key in new_module_args: del new_module_args[key] # execute the unarchive module now, with the updated args result.update(self._execute_module(module_args=new_module_args, task_vars=task_vars)) self._remove_tmp_path(tmp) 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) if self._play_context.check_mode: result['skipped'] = True result['msg'] = 'check mode not (yet) supported for this module' return result source = self._task.args.get('src', None) dest = self._task.args.get('dest', None) flat = boolean(self._task.args.get('flat')) fail_on_missing = boolean(self._task.args.get('fail_on_missing')) validate_checksum = boolean(self._task.args.get('validate_checksum', self._task.args.get('validate_md5'))) if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args: result['failed'] = True result['msg'] = "validate_checksum and validate_md5 cannot both be specified" return result if source is None or dest is None: result['failed'] = True result['msg'] = "src and dest are required" return result source = self._connection._shell.join_path(source) source = self._remote_expand_user(source) remote_checksum = None if not self._play_context.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='slurp', module_args=dict(src=source), task_vars=task_vars, tmp=tmp) 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) # the source path may have been expanded on the # target system, so we compare it here and use the # expanded version if it's different remote_source = slurpres.get('source') if remote_source and remote_source != source: source = remote_source # 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 dest = os.path.expanduser(dest) if flat: 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) dest = dest.replace("//","/") if remote_checksum in ('0', '1', '2', '3', '4'): # 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 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': if fail_on_missing: result['failed'] = True del result['changed'] result['msg'] = "the remote file does not exist" else: result['msg'] = "the remote file does not exist, not transferring, ignored" elif remote_checksum == '2': result['msg'] = "no read permission on remote file, not transferring, ignored" 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" return result # 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 AnsibleError("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(dict(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)) return result
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) if self._play_context.check_mode: result['skipped'] = True result['msg'] = "skipped, this module does not support check_mode." return result 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) if src is None or dest is None: result['failed'] = True result['msg'] = "src and dest are required" return result remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not tmp: tmp = self._make_tmp_path(remote_user) self._cleanup_remote_tmp = True if boolean(remote_src): result.update(self._execute_module(tmp=tmp, task_vars=task_vars, delete_remote_tmp=False)) self._remove_tmp_path(tmp) return result else: try: src = self._find_needle('files', src) except AnsibleError as e: result['failed'] = True result['msg'] = to_native(e) return result if not os.path.isdir(src): result['failed'] = True result['msg'] = u"Source (%s) is not a directory" % src return result _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) path_checksum = checksum_s(path) dest = self._remote_expand_user(dest) dest_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=follow, tmp=tmp) 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']: if opt in new_module_args: del new_module_args[opt] new_module_args.update( dict( dest=dest, original_basename=os.path.basename(src), ) ) 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(tmp, 'src') xfered = self._transfer_file(path, remote_path) # fix file permissions when the copy is done as a different user self._fixup_perms2((tmp, remote_path), remote_user) new_module_args.update( dict( src=xfered,)) res = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=False) if diff: res['diff'] = diff result.update(res) else: result.update(self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=False)) self._remove_tmp_path(tmp) return result
def post_validate(self, templar): ''' we can't tell that everything is of the right type until we have all the variables. Run basic types (from isa) as well as any _post_validate_<foo> functions. ''' # save the omit value for later checking omit_value = templar._available_variables.get('omit') for (name, attribute) in iteritems(self._valid_attrs): if getattr(self, name) is None: if not attribute.required: continue else: raise AnsibleParserError("the field '%s' is required but was not set" % name) elif not attribute.always_post_validate and self.__class__.__name__ not in ('Task', 'Handler', 'PlayContext'): # Intermediate objects like Play() won't have their fields validated by # default, as their values are often inherited by other objects and validated # later, so we don't want them to fail out early continue try: # Run the post-validator if present. These methods are responsible for # using the given templar to template the values, if required. method = getattr(self, '_post_validate_%s' % name, None) if method: value = method(attribute, getattr(self, name), templar) elif attribute.isa == 'class': value = getattr(self, name) else: # if the attribute contains a variable, template it now value = templar.template(getattr(self, name)) # if this evaluated to the omit value, set the value back to # the default specified in the FieldAttribute and move on if omit_value is not None and value == omit_value: setattr(self, name, attribute.default) continue # and make sure the attribute is of the type it should be if value is not None: 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) 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 in ('list', 'barelist'): if value is None: value = [] elif not isinstance(value, list): if isinstance(value, string_types) and attribute.isa == 'barelist': display.deprecated( "Using comma separated values for a list has been deprecated. " "You should instead use the correct YAML syntax for lists. " ) value = value.split(',') else: value = [ value ] if attribute.listof is not None: for item in value: if not isinstance(item, attribute.listof): raise AnsibleParserError("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 AnsibleParserError("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) # and assign the massaged value back to the attribute field setattr(self, name, value) except (TypeError, ValueError) as e: raise AnsibleParserError("the field '%s' has an invalid value (%s), and could not be converted to an %s." " Error was: %s" % (name, value, attribute.isa, e), obj=self.get_ds()) except (AnsibleUndefinedVariable, UndefinedError) as e: if templar._fail_on_undefined_errors and name != 'name': raise AnsibleParserError("the field '%s' has an invalid value, which appears to include a variable that is undefined." " The error was: %s" % (name,e), obj=self.get_ds()) self._finalized = True
def run(self, tmp=None, task_vars=None): self._supports_check_mode = False result = super(ActionModule, self).run(tmp, task_vars) if result.get('skipped', False): return result 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.get('decrypt', True) if src is None or dest is None: result['failed'] = True result['msg'] = "src and dest are required" return result if boolean(remote_src): result.update(self._execute_module(tmp=tmp, task_vars=task_vars)) return result else: try: src = self._find_needle('files', src) except AnsibleError as e: result['failed'] = True result['msg'] = to_native(e) return result if not tmp: tmp = self._make_tmp_path() if not os.path.isdir(src): result['failed'] = True result['msg'] = u"Source (%s) is not a directory" % src return result _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, tmp=tmp) 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.update( dict( dest=dest, original_basename=os.path.basename(src), ) ) 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(tmp, 'src') xfered = self._transfer_file(path, remote_path) # fix file permissions when the copy is done as a different user self._fixup_perms2((tmp, remote_path)) new_module_args.update( dict( src=xfered,)) res = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=False) if diff: res['diff'] = diff result.update(res) else: result.update(self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=False)) self._remove_tmp_path(tmp) return result
def run(self, terms, variables, **kwargs): def _raise_terms_error(msg=""): raise AnsibleError( "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 AnsibleError( "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)) subvalue = item0 lastsubkey = False sublist = [] for subkey in subelements: if subkey == subelements[-1]: lastsubkey = True if not subkey in subvalue: if skip_missing: continue else: raise AnsibleError( "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 AnsibleError( "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 AnsibleError( "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 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) source = self._task.args.get('src', None) content = self._task.args.get('content', None) dest = self._task.args.get('dest', None) raw = boolean(self._task.args.get('raw', 'no')) force = boolean(self._task.args.get('force', 'yes')) remote_src = boolean(self._task.args.get('remote_src', False)) follow = boolean(self._task.args.get('follow', False)) if (source is None and content is None) or dest is None: result['failed'] = True result['msg'] = "src (or content) and dest are required" return result elif source is not None and content is not None: result['failed'] = True result['msg'] = "src and content are mutually exclusive" return result elif content is not None and dest is not None and dest.endswith("/"): result['failed'] = True result['msg'] = "dest must be a file if content is defined" return result # Check if the source ends with a "/" source_trailing_slash = False if source: source_trailing_slash = self._connection._shell.path_has_trailing_slash(source) # Define content_tempfile in case we set it after finding content populated. content_tempfile = None # If content is defined make a temp 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 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='copy', module_args=self._task.args, task_vars=task_vars, delete_remote_tmp=False)) return result else: # find in expected paths try: source = self._find_needle('files', source) except AnsibleError as e: result['failed'] = True result['msg'] = to_text(e) return result # A list of source file tuples (full_path, relative_path) which will try to copy to the destination source_files = [] # 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 the amount of spaces to remove to get the relative path. if source_trailing_slash: sz = len(source) else: sz = len(source.rsplit('/', 1)[0]) + 1 # Walk the directory and append the file tuples to source_files. for base_path, sub_folders, files in os.walk(to_bytes(source)): for file in files: full_path = to_text(os.path.join(base_path, file), errors='surrogate_or_strict') rel_path = full_path[sz:] if rel_path.startswith('/'): rel_path = rel_path[1:] source_files.append((full_path, rel_path)) # recurse into subdirs for sf in sub_folders: source_files += self._get_recursive_files(os.path.join(source, to_text(sf)), sz=sz) # 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, '') else: source_files.append((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 # Tell _execute_module to delete the file if there is one file. delete_remote_tmp = (len(source_files) == 1) # If this is a recursive action create a tmp path that we can share as the _exec_module create is too late. remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not delete_remote_tmp: if tmp is None or "-tmp-" not in tmp: tmp = self._make_tmp_path(remote_user) self._cleanup_remote_tmp = True # expand any user home dir specifier dest = self._remote_expand_user(dest) # Keep original value for mode parameter mode_value = self._task.args.get('mode', None) diffs = [] for source_full, source_rel in source_files: source_full = self._loader.get_real_file(source_full) # Generate a hash of the local file. local_checksum = checksum(source_full) # Get the local mode and set if user wanted it preserved # https://github.com/ansible/ansible-modules-core/issues/1124 if self._task.args.get('mode', None) == 'preserve': lmode = '0%03o' % stat.S_IMODE(os.stat(source_full).st_mode) self._task.args['mode'] = lmode # If local_checksum is not defined we can't find the file so we should fail out. if local_checksum is None: result['failed'] = True result['msg'] = "could not find src=%s" % source_full self._remove_tmp_path(tmp) return result # 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 = self._connection._shell.join_path(dest) # Attempt to get remote file info dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow, tmp=tmp) 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) self._remove_tmp_path(tmp) 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, tmp=tmp) if dest_status['exists'] and not force: # remote_file does not exist so continue to next iteration. continue if local_checksum != dest_status['checksum']: # The checksums don't match and we will change or error out. changed = True # Create a tmp path if missing only if this is not recursive. # If this is recursive we already have a tmp path. if delete_remote_tmp: if tmp is None or "-tmp-" not in tmp: tmp = self._make_tmp_path(remote_user) self._cleanup_remote_tmp = True if self._play_context.diff and not raw: diffs.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) changed = True module_return = dict(changed=True) continue # Define a remote directory that we will copy the file to. tmp_src = self._connection._shell.join_path(tmp, '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) # fix file permissions when the copy is done as a different user if remote_path: self._fixup_perms2((tmp, remote_path), remote_user) if raw: # Continue to next iteration if raw is defined. continue # 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 = self._task.args.copy() new_module_args.update( dict( src=tmp_src, dest=dest, original_basename=source_rel, ) ) if 'content' in new_module_args: del new_module_args['content'] module_return = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=delete_remote_tmp) module_executed = True 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: # Continue to next iteration if raw is defined. self._remove_tmp_path(tmp) continue # Build temporary module_args. new_module_args = self._task.args.copy() new_module_args.update( dict( src=source_rel, dest=dest, original_basename=source_rel ) ) # Execute the file module. module_return = self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=delete_remote_tmp) module_executed = True if not module_return.get('checksum'): module_return['checksum'] = local_checksum if module_return.get('failed'): result.update(module_return) if not delete_remote_tmp: self._remove_tmp_path(tmp) return result if module_return.get('changed'): changed = True # 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 module_return and 'dest' not in module_return: module_return['dest'] = module_return['path'] # reset the mode self._task.args['mode'] = mode_value # Delete tmp path if we were recursive or if we did not execute a module. if not delete_remote_tmp or (delete_remote_tmp and not module_executed): self._remove_tmp_path(tmp) if module_executed and len(source_files) == 1: result.update(module_return) else: result.update(dict(dest=dest, src=source, changed=changed)) if diffs: result['diff'] = diffs return result