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['ansible_facts'] = facts result['_ansible_facts_cacheable'] = cacheable return result
def _filter_host(self, inventory_hostname, hostvars): self.templar.available_variables = hostvars for condition in self._filters: # FUTURE: should warn/fail if conditional doesn't return True or False conditional = "{{% if {0} %}} True {{% else %}} False {{% endif %}}".format( condition) try: if boolean(self.templar.template(conditional)): return True except Exception as e: if boolean(self.get_option('fail_on_template_errors')): raise AnsibleParserError( "Error evaluating filter condition '{0}' for host {1}: {2}" .format(condition, inventory_hostname, to_native(e))) continue return False
def boolean_or_cacert(self, validate_certs, cacert): validate_certs = boolean(validate_certs, strict=False) '''' 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) 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['ansible_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 _get_hosts(self): for vm_rg in self.get_option('include_vm_resource_groups'): self._enqueue_vm_list(vm_rg) for vmss_rg in self.get_option('include_vmss_resource_groups'): self._enqueue_vmss_list(vmss_rg) if self._batch_fetch: self._process_queue_batch() else: self._process_queue_serial() constructable_config_strict = boolean( self.get_option('fail_on_template_errors')) constructable_config_compose = self.get_option('hostvar_expressions') constructable_config_groups = self.get_option('conditional_groups') constructable_config_keyed_groups = self.get_option('keyed_groups') for h in self._hosts: inventory_hostname = self._get_hostname(h) if self._filter_host(inventory_hostname, h.hostvars): continue self.inventory.add_host(inventory_hostname) # FUTURE: configurable default IP list? can already do this via hostvar_expressions self.inventory.set_variable( inventory_hostname, "ansible_host", next( chain(h.hostvars['public_ipv4_addresses'], h.hostvars['private_ipv4_addresses']), None)) for k, v in iteritems(h.hostvars): # FUTURE: configurable hostvar prefix? Makes docs harder... self.inventory.set_variable(inventory_hostname, k, v) # constructable delegation self._set_composite_vars(constructable_config_compose, h.hostvars, inventory_hostname, strict=constructable_config_strict) self._add_host_to_composed_groups( constructable_config_groups, h.hostvars, inventory_hostname, strict=constructable_config_strict) self._add_host_to_keyed_groups(constructable_config_keyed_groups, h.hostvars, inventory_hostname, strict=constructable_config_strict)
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, 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 src = self._task.args.get('src', None) remote_src = boolean(self._task.args.get('remote_src', 'no'), strict=False) try: if src is None: raise AnsibleActionFail("src is required") elif remote_src: # everything is remote, so we just execute the module # without changing any of the module arguments raise _AnsibleActionDone(result=self._execute_module( task_vars=task_vars)) try: src = self._find_needle('files', src) except AnsibleError as e: raise AnsibleActionFail(to_native(e)) tmp_src = self._connection._shell.join_path( self._connection._shell.tmpdir, os.path.basename(src)) self._transfer_file(src, tmp_src) self._fixup_perms2((self._connection._shell.tmpdir, tmp_src)) 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)) except AnsibleAction as e: result.update(e.result) finally: self._remove_tmp_path(self._connection._shell.tmpdir) return 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', 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='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 AnsibleError 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='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 module_return = self._execute_module(module_name='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 _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 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, to_text(e)) return result # Get the local mode and set if user wanted it preserved # https://github.com/ansible/ansible-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='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/ansible/ansible-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='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 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 (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] if skip: return [] raise AnsibleLookupError( "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): self._supports_check_mode = True self._supports_async = True result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect state = self._task.args.get('state', 'installed') reboot = self._task.args.get('reboot', False) reboot_timeout = self._task.args.get('reboot_timeout', self.DEFAULT_REBOOT_TIMEOUT) use_task = boolean(self._task.args.get('use_scheduled_task', False), strict=False) if state not in ['installed', 'searched', 'downloaded']: result['failed'] = True result[ 'msg'] = "state must be either installed, searched or downloaded" return result try: reboot = boolean(reboot) except TypeError as exc: result['failed'] = True result['msg'] = "cannot parse reboot as a boolean: %s" % to_text( exc) return result if not isinstance(reboot_timeout, int): result['failed'] = True result['msg'] = "reboot_timeout must be an integer" return result if reboot and self._task.async_val > 0: result['failed'] = True result['msg'] = "async is not supported for this task when " \ "reboot=yes" return result # Run the module new_module_args = self._task.args.copy() new_module_args.pop('reboot', None) new_module_args.pop('reboot_timeout', None) result = self._run_win_updates(new_module_args, task_vars, use_task) # if the module failed to run at all then changed won't be populated # so we just return the result as is # https://github.com/ansible/ansible/issues/38232 failed = result.get('failed', False) if ("updates" not in result.keys() and self._task.async_val == 0) or failed: result['failed'] = True return result changed = result.get('changed', False) updates = result.get('updates', dict()) filtered_updates = result.get('filtered_updates', dict()) found_update_count = result.get('found_update_count', 0) installed_update_count = result.get('installed_update_count', 0) # Handle automatic reboots if the reboot flag is set if reboot and state == 'installed' and not \ self._play_context.check_mode: previously_errored = False while result['installed_update_count'] > 0 or \ result['found_update_count'] > 0 or \ result['reboot_required'] is True: display.vvv("win_updates: check win_updates results for " "automatic reboot: %s" % json.dumps(result)) # check if the module failed, break from the loop if it # previously failed and return error to the user if result.get('failed', False): if previously_errored: break previously_errored = True else: previously_errored = False reboot_error = None # check if a reboot was required before installing the updates if result.get('msg', '') == "A reboot is required before " \ "more updates can be installed": reboot_error = "reboot was required before more updates " \ "can be installed" if result.get('reboot_required', False): if reboot_error is None: reboot_error = "reboot was required to finalise " \ "update install" try: changed = True self._reboot_server(task_vars, reboot_timeout, use_task) except AnsibleError as exc: result['failed'] = True result['msg'] = "Failed to reboot remote host when " \ "%s: %s" \ % (reboot_error, to_text(exc)) break result.pop('msg', None) # rerun the win_updates module after the reboot is complete result = self._run_win_updates(new_module_args, task_vars, use_task) if result.get('failed', False): return result result_updates = result.get('updates', dict()) result_filtered_updates = result.get('filtered_updates', dict()) updates = self._merge_dict(updates, result_updates) filtered_updates = self._merge_dict(filtered_updates, result_filtered_updates) found_update_count += result.get('found_update_count', 0) installed_update_count += result.get('installed_update_count', 0) if result['changed']: changed = True # finally create the return dict based on the aggregated execution # values if we are not in async if self._task.async_val == 0: result['changed'] = changed result['updates'] = updates result['filtered_updates'] = filtered_updates result['found_update_count'] = found_update_count result['installed_update_count'] = installed_update_count 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('ansible_psrp_', '') for v in self.get_option('_extras')]) unsupported_args = extra_args.difference(supported_args) for arg in unsupported_args: display.warning("ansible_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("ansible_psrp_read_timeout is unsupported by the current psrp version installed, " "using ansible_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("ansible_psrp_reconnection_retries is unsupported by the current psrp version installed.") if self._psrp_reconnection_backoff is not None: display.warning("ansible_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')['ansible_psrp_%s' % arg] self._psrp_conn_kwargs[arg] = option
def run(self, tmp=None, task_vars=None): ''' handler for template 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 # Options type validation # stings for s_type in ('src', 'dest', 'state', 'newline_sequence', 'variable_start_string', 'variable_end_string', 'block_start_string', 'block_end_string'): if s_type in self._task.args: value = ensure_type(self._task.args[s_type], 'string') if value is not None and not isinstance(value, string_types): raise AnsibleActionFail( "%s is expected to be a string, but got %s instead" % (s_type, type(value))) self._task.args[s_type] = value # booleans try: follow = boolean(self._task.args.get('follow', False), strict=False) trim_blocks = boolean(self._task.args.get('trim_blocks', True), strict=False) lstrip_blocks = boolean(self._task.args.get( 'lstrip_blocks', False), strict=False) except TypeError as e: raise AnsibleActionFail(to_native(e)) # assign to local vars for ease of use source = self._task.args.get('src', None) dest = self._task.args.get('dest', None) state = self._task.args.get('state', None) newline_sequence = self._task.args.get('newline_sequence', self.DEFAULT_NEWLINE_SEQUENCE) variable_start_string = self._task.args.get('variable_start_string', None) variable_end_string = self._task.args.get('variable_end_string', None) block_start_string = self._task.args.get('block_start_string', None) block_end_string = self._task.args.get('block_end_string', None) output_encoding = self._task.args.get('output_encoding', 'utf-8') or 'utf-8' # Option `lstrip_blocks' was added in Jinja2 version 2.7. if lstrip_blocks: try: import jinja2.defaults except ImportError: raise AnsibleError( 'Unable to import Jinja2 defaults for determining Jinja2 features.' ) try: jinja2.defaults.LSTRIP_BLOCKS except AttributeError: raise AnsibleError( "Option `lstrip_blocks' is only available in Jinja2 versions >=2.7" ) wrong_sequences = ["\\n", "\\r", "\\r\\n"] allowed_sequences = ["\n", "\r", "\r\n"] # We need to convert unescaped sequences to proper escaped sequences for Jinja2 if newline_sequence in wrong_sequences: newline_sequence = allowed_sequences[wrong_sequences.index( newline_sequence)] try: # logical validation if state is not None: raise AnsibleActionFail( "'state' cannot be specified on a template") elif source is None or dest is None: raise AnsibleActionFail("src and dest are required") elif newline_sequence not in allowed_sequences: raise AnsibleActionFail( "newline_sequence needs to be one of: \n, \r or \r\n") else: try: source = self._find_needle('templates', source) except AnsibleError as e: raise AnsibleActionFail(to_text(e)) mode = self._task.args.get('mode', None) if mode == 'preserve': mode = '0%03o' % stat.S_IMODE(os.stat(source).st_mode) # Get vault decrypted tmp file try: tmp_source = self._loader.get_real_file(source) except AnsibleFileNotFound as e: raise AnsibleActionFail("could not find src=%s, %s" % (source, to_text(e))) b_tmp_source = to_bytes(tmp_source, errors='surrogate_or_strict') # template the source data locally & get ready to transfer try: with open(b_tmp_source, 'rb') as f: try: template_data = to_text(f.read(), errors='surrogate_or_strict') except UnicodeError: raise AnsibleActionFail( "Template source files must be utf-8 encoded") # set jinja2 internal search path for includes searchpath = task_vars.get('ansible_search_path', []) searchpath.extend( [self._loader._basedir, os.path.dirname(source)]) # We want to search into the 'templates' subdir of each search path in # addition to our original search paths. newsearchpath = [] for p in searchpath: newsearchpath.append(os.path.join(p, 'templates')) newsearchpath.append(p) searchpath = newsearchpath # add ansible 'template' vars temp_vars = task_vars.copy() temp_vars.update(generate_ansible_template_vars(source, dest)) with self._templar.set_temporary_context( searchpath=searchpath, newline_sequence=newline_sequence, block_start_string=block_start_string, block_end_string=block_end_string, variable_start_string=variable_start_string, variable_end_string=variable_end_string, trim_blocks=trim_blocks, lstrip_blocks=lstrip_blocks, available_variables=temp_vars): resultant = self._templar.do_template( template_data, preserve_trailing_newlines=True, escape_backslashes=False) except AnsibleAction: raise except Exception as e: raise AnsibleActionFail("%s: %s" % (type(e).__name__, to_text(e))) finally: self._loader.cleanup_tmp_file(b_tmp_source) new_task = self._task.copy() # mode is either the mode from task.args or the mode of the source file if the task.args # mode == 'preserve' new_task.args['mode'] = mode # remove 'template only' options: for remove in ('newline_sequence', 'block_start_string', 'block_end_string', 'variable_start_string', 'variable_end_string', 'trim_blocks', 'lstrip_blocks', 'output_encoding'): new_task.args.pop(remove, None) local_tempdir = tempfile.mkdtemp(dir=C.DEFAULT_LOCAL_TMP) try: result_file = os.path.join(local_tempdir, os.path.basename(source)) with open(to_bytes(result_file, errors='surrogate_or_strict'), 'wb') as f: f.write( to_bytes(resultant, encoding=output_encoding, errors='surrogate_or_strict')) new_task.args.update( dict( src=result_file, dest=dest, follow=follow, ), ) copy_action = self._shared_loader_obj.action_loader.get( 'copy', task=new_task, connection=self._connection, play_context=self._play_context, loader=self._loader, templar=self._templar, shared_loader_obj=self._shared_loader_obj) result.update(copy_action.run(task_vars=task_vars)) finally: shutil.rmtree( to_bytes(local_tempdir, errors='surrogate_or_strict')) except AnsibleAction as e: result.update(e.result) 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 AnsibleError('user requested abort!') except AnsibleTimeoutExceeded: # 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): ''' 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 AnsibleActionFail( "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 AnsibleActionFail( "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 AnsibleActionSkip("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 AnsibleError as e: raise AnsibleActionFail(to_text(e)) try: remote_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=True) except AnsibleError as e: raise AnsibleActionFail(to_text(e)) if not remote_stat['exists'] or not remote_stat['isdir']: raise AnsibleActionFail("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 result.update( self._execute_module(module_args=new_module_args, task_vars=task_vars)) except AnsibleAction as e: result.update(e.result) finally: 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: 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'), 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) # 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" 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._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='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) # 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 os.path.isdir(to_bytes(dest, errors='surrogate_or_strict') ) and not dest.endswith(os.sep): result[ 'msg'] = "dest is an existing directory, use a trailing slash if you want to fetch src into that directory" result['file'] = dest result['failed'] = True return result 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 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)) finally: self._remove_tmp_path(self._connection._shell.tmpdir) 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, we currently only use this # to set -f so we can get a forward-able ticket (cred delegation) kinit_flags = [] if boolean( self.get_option('_extras').get( 'ansible_winrm_kerberos_delegation', False)): kinit_flags.append('-f') kinit_cmdline = [self._kinit_cmd] kinit_cmdline.extend(kinit_flags) 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 AnsibleConnectionFailure(err_msg) try: child.expect(".*:") child.sendline(password) except OSError as err: # child exited before the pass was sent, Ansible 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 AnsibleConnectionFailure(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 AnsibleConnectionFailure(err_msg) display.vvvvv("kinit succeeded for principal %s" % principal)
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.get('decrypt', True) try: if src is None or dest is None: raise AnsibleActionFail("src and dest are required") if boolean(remote_src, strict=False): result.update( self._execute_module(module_name='assemble', task_vars=task_vars)) raise _AnsibleActionDone() else: try: src = self._find_needle('files', src) except AnsibleError as e: raise AnsibleActionFail(to_native(e)) if not os.path.isdir(src): raise AnsibleActionFail(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='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='file', module_args=new_module_args, task_vars=task_vars)) except AnsibleAction as e: result.update(e.result) finally: self._remove_tmp_path(self._connection._shell.tmpdir) return 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 AnsibleError 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 AnsibleFileNotFound try: source_full = self._loader.get_real_file(source, decrypt=decrypt) except AnsibleFileNotFound 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): ''' generates params and passes them on to the rsync module ''' # When modifying this function be aware of the tricky convolutions # your thoughts have to go through: # # In normal ansible, we connect from controller to inventory_hostname # (playbook's hosts: field) or controller to delegate_to host and run # a module on one of those hosts. # # So things that are directly related to the core of ansible are in # terms of that sort of connection that always originate on the # controller. # # In synchronize we use ansible to connect to either the controller or # to the delegate_to host and then run rsync which makes its own # connection from controller to inventory_hostname or delegate_to to # inventory_hostname. # # That means synchronize needs to have some knowledge of the # controller to inventory_host/delegate host that ansible typically # establishes and use those to construct a command line for rsync to # connect from the inventory_host to the controller/delegate. The # challenge for coders is remembering which leg of the trip is # associated with the conditions that you're checking at any one time. if task_vars is None: task_vars = dict() # We make a copy of the args here because we may fail and be asked to # retry. If that happens we don't want to pass the munged args through # to our next invocation. Munged args are single use only. _tmp_args = self._task.args.copy() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect # Store remote connection type self._remote_transport = self._connection.transport # Handle docker connection options if self._remote_transport == 'docker': self._docker_cmd = self._connection.docker_cmd if self._play_context.docker_extra_args: self._docker_cmd = "%s %s" % ( self._docker_cmd, self._play_context.docker_extra_args) # self._connection accounts for delegate_to so # remote_transport is the transport ansible thought it would need # between the controller and the delegate_to host or the controller # and the remote_host if delegate_to isn't set. remote_transport = False if self._connection.transport != 'local': remote_transport = True try: delegate_to = self._task.delegate_to except (AttributeError, KeyError): delegate_to = None # ssh paramiko docker buildah and local are fully supported transports. Anything # else only works with delegate_to if delegate_to is None and self._connection.transport not in \ ('ssh', 'paramiko', 'local', 'docker', 'buildah'): result['failed'] = True result['msg'] = ( "synchronize uses rsync to function. rsync needs to connect to the remote " "host via ssh, docker client or a direct filesystem " "copy. This remote host is being accessed via %s instead " "so it cannot work." % self._connection.transport) return result use_ssh_args = _tmp_args.pop('use_ssh_args', None) # Parameter name needed by the ansible module _tmp_args['_local_rsync_path'] = task_vars.get( 'ansible_rsync_path') or 'rsync' _tmp_args['_local_rsync_password'] = task_vars.get( 'ansible_ssh_pass') or task_vars.get('ansible_password') # rsync thinks that one end of the connection is localhost and the # other is the host we're running the task for (Note: We use # ansible's delegate_to mechanism to determine which host rsync is # running on so localhost could be a non-controller machine if # delegate_to is used) src_host = '127.0.0.1' inventory_hostname = task_vars.get('inventory_hostname') dest_host_inventory_vars = task_vars['hostvars'].get( inventory_hostname) try: dest_host = dest_host_inventory_vars['ansible_host'] except KeyError: dest_host = dest_host_inventory_vars.get('ansible_ssh_host', inventory_hostname) dest_host_ids = [ hostid for hostid in (dest_host_inventory_vars.get('inventory_hostname'), dest_host_inventory_vars.get('ansible_host'), dest_host_inventory_vars.get('ansible_ssh_host')) if hostid is not None ] localhost_ports = set() for host in C.LOCALHOST: localhost_vars = task_vars['hostvars'].get(host, {}) for port_var in C.MAGIC_VARIABLE_MAPPING['port']: port = localhost_vars.get(port_var, None) if port: break else: port = C.DEFAULT_REMOTE_PORT localhost_ports.add(port) # dest_is_local tells us if the host rsync runs on is the same as the # host rsync puts the files on. This is about *rsync's connection*, # not about the ansible connection to run the module. dest_is_local = False if delegate_to is None and remote_transport is False: dest_is_local = True elif delegate_to is not None and delegate_to in dest_host_ids: dest_is_local = True # CHECK FOR NON-DEFAULT SSH PORT inv_port = task_vars.get('ansible_ssh_port', None) or C.DEFAULT_REMOTE_PORT if _tmp_args.get('dest_port', None) is None: if inv_port is not None: _tmp_args['dest_port'] = inv_port # Set use_delegate if we are going to run rsync on a delegated host # instead of localhost use_delegate = False if delegate_to is not None and delegate_to in dest_host_ids: # edge case: explicit delegate and dest_host are the same # so we run rsync on the remote machine targeting its localhost # (itself) dest_host = '127.0.0.1' use_delegate = True elif delegate_to is not None and remote_transport: # If we're delegating to a remote host then we need to use the # delegate_to settings use_delegate = True # Delegate to localhost as the source of the rsync unless we've been # told (via delegate_to) that a different host is the source of the # rsync if not use_delegate and remote_transport: # Create a connection to localhost to run rsync on new_stdin = self._connection._new_stdin # Unlike port, there can be only one shell localhost_shell = None for host in C.LOCALHOST: localhost_vars = task_vars['hostvars'].get(host, {}) for shell_var in C.MAGIC_VARIABLE_MAPPING['shell']: localhost_shell = localhost_vars.get(shell_var, None) if localhost_shell: break if localhost_shell: break else: localhost_shell = os.path.basename(C.DEFAULT_EXECUTABLE) self._play_context.shell = localhost_shell # Unlike port, there can be only one executable localhost_executable = None for host in C.LOCALHOST: localhost_vars = task_vars['hostvars'].get(host, {}) for executable_var in C.MAGIC_VARIABLE_MAPPING['executable']: localhost_executable = localhost_vars.get( executable_var, None) if localhost_executable: break if localhost_executable: break else: localhost_executable = C.DEFAULT_EXECUTABLE self._play_context.executable = localhost_executable new_connection = connection_loader.get('local', self._play_context, new_stdin) self._connection = new_connection # Override _remote_is_local as an instance attribute specifically for the synchronize use case # ensuring we set local tmpdir correctly self._connection._remote_is_local = True self._override_module_replaced_vars(task_vars) # SWITCH SRC AND DEST HOST PER MODE if _tmp_args.get('mode', 'push') == 'pull': (dest_host, src_host) = (src_host, dest_host) # MUNGE SRC AND DEST PER REMOTE_HOST INFO src = _tmp_args.get('src', None) dest = _tmp_args.get('dest', None) if src is None or dest is None: return dict( failed=True, msg="synchronize requires both src and dest parameters are set" ) # Determine if we need a user@ user = None if not dest_is_local: # Src and dest rsync "path" handling if boolean(_tmp_args.get('set_remote_user', 'yes'), strict=False): if use_delegate: user = task_vars.get('ansible_delegated_vars', dict()).get('ansible_ssh_user', None) if not user: user = task_vars.get( 'ansible_ssh_user' ) or self._play_context.remote_user if not user: user = C.DEFAULT_REMOTE_USER else: user = task_vars.get( 'ansible_ssh_user') or self._play_context.remote_user # Private key handling private_key = self._play_context.private_key_file if private_key is not None: _tmp_args['private_key'] = private_key # use the mode to define src and dest's url if _tmp_args.get('mode', 'push') == 'pull': # src is a remote path: <user>@<host>, dest is a local path src = self._process_remote(_tmp_args, src_host, src, user, inv_port in localhost_ports) dest = self._process_origin(dest_host, dest, user) else: # src is a local path, dest is a remote path: <user>@<host> src = self._process_origin(src_host, src, user) dest = self._process_remote(_tmp_args, dest_host, dest, user, inv_port in localhost_ports) else: # Still need to munge paths (to account for roles) even if we aren't # copying files between hosts if not src.startswith('/'): src = self._get_absolute_path(path=src) if not dest.startswith('/'): dest = self._get_absolute_path(path=dest) _tmp_args['src'] = src _tmp_args['dest'] = dest # Allow custom rsync path argument rsync_path = _tmp_args.get('rsync_path', None) # backup original become as we are probably about to unset it become = self._play_context.become if not dest_is_local: # don't escalate for docker. doing --rsync-path with docker exec fails # and we can switch directly to the user via docker arguments if self._play_context.become and not rsync_path and self._remote_transport != 'docker': # If no rsync_path is set, become was originally set, and dest is # remote then add privilege escalation here. if self._play_context.become_method == 'sudo': rsync_path = 'sudo rsync' # TODO: have to add in the rest of the become methods here # We cannot use privilege escalation on the machine running the # module. Instead we run it on the machine rsync is connecting # to. self._play_context.become = False _tmp_args['rsync_path'] = rsync_path if use_ssh_args: ssh_args = [ getattr(self._play_context, 'ssh_args', ''), getattr(self._play_context, 'ssh_common_args', ''), getattr(self._play_context, 'ssh_extra_args', ''), ] _tmp_args['ssh_args'] = ' '.join([a for a in ssh_args if a]) # If launching synchronize against docker container # use rsync_opts to support container to override rsh options if self._remote_transport in ['docker', 'buildah']: # Replicate what we do in the module argumentspec handling for lists if not isinstance(_tmp_args.get('rsync_opts'), MutableSequence): tmp_rsync_opts = _tmp_args.get('rsync_opts', []) if isinstance(tmp_rsync_opts, string_types): tmp_rsync_opts = tmp_rsync_opts.split(',') elif isinstance(tmp_rsync_opts, (int, float)): tmp_rsync_opts = [to_text(tmp_rsync_opts)] _tmp_args['rsync_opts'] = tmp_rsync_opts if '--blocking-io' not in _tmp_args['rsync_opts']: _tmp_args['rsync_opts'].append('--blocking-io') if self._remote_transport in ['docker']: if become and self._play_context.become_user: _tmp_args['rsync_opts'].append( "--rsh=%s exec -u %s -i" % (self._docker_cmd, self._play_context.become_user)) elif user is not None: _tmp_args['rsync_opts'].append("--rsh=%s exec -u %s -i" % (self._docker_cmd, user)) else: _tmp_args['rsync_opts'].append("--rsh=%s exec -i" % self._docker_cmd) elif self._remote_transport in ['buildah']: _tmp_args['rsync_opts'].append("--rsh=buildah run --") # run the module and store the result result.update( self._execute_module('synchronize', module_args=_tmp_args, task_vars=task_vars)) return result