def shutdown(self): """ Shuts down the local domain socket """ lock_path = unfrackpath("%s/.assible_pc_lock_%s" % os.path.split(self.socket_path)) if os.path.exists(self.socket_path): try: if self.sock: self.sock.close() if self.connection: self.connection.close() if self.connection.get_option("persistent_log_messages"): for _level, message in self.connection.pop_messages(): display.display(message, log_only=True) except Exception: pass finally: if os.path.exists(self.socket_path): os.remove(self.socket_path) setattr(self.connection, '_socket_path', None) setattr(self.connection, '_connected', False) if os.path.exists(lock_path): os.remove(lock_path) display.display('shutdown complete', log_only=True)
def _is_role(self, path): ''' imperfect role detection, roles are still valid w/o tasks|meta/main.yml|yaml|etc ''' b_path = to_bytes(path, errors='surrogate_or_strict') b_path_dirname = os.path.dirname(b_path) b_upath = to_bytes(unfrackpath(path, follow=False), errors='surrogate_or_strict') untasked_paths = ( os.path.join(b_path, b'main.yml'), os.path.join(b_path, b'main.yaml'), os.path.join(b_path, b'main'), ) tasked_paths = ( os.path.join(b_upath, b'tasks/main.yml'), os.path.join(b_upath, b'tasks/main.yaml'), os.path.join(b_upath, b'tasks/main'), os.path.join(b_upath, b'meta/main.yml'), os.path.join(b_upath, b'meta/main.yaml'), os.path.join(b_upath, b'meta/main'), os.path.join(b_path_dirname, b'tasks/main.yml'), os.path.join(b_path_dirname, b'tasks/main.yaml'), os.path.join(b_path_dirname, b'tasks/main'), os.path.join(b_path_dirname, b'meta/main.yml'), os.path.join(b_path_dirname, b'meta/main.yaml'), os.path.join(b_path_dirname, b'meta/main'), ) exists_untasked = map(os.path.exists, untasked_paths) exists_tasked = map(os.path.exists, tasked_paths) if RE_TASKS.search(path) and any(exists_untasked) or any( exists_tasked): return True return False
def put_file(self, in_path, out_path): ''' transfer a file from local to local ''' super(Connection, self).put_file(in_path, out_path) in_path = unfrackpath(in_path, basedir=self.cwd) out_path = unfrackpath(out_path, basedir=self.cwd) display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._play_context.remote_addr) if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')): raise AssibleFileNotFound("file or module does not exist: {0}".format(to_native(in_path))) try: shutil.copyfile(to_bytes(in_path, errors='surrogate_or_strict'), to_bytes(out_path, errors='surrogate_or_strict')) except shutil.Error: raise AssibleError("failed to copy: {0} and {1} are the same".format(to_native(in_path), to_native(out_path))) except IOError as e: raise AssibleError("failed to transfer file to {0}: {1}".format(to_native(out_path), to_native(e)))
def set_options(self, task_keys=None, var_options=None, direct=None): ''' override to set self.tree ''' super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct) if TREE_DIR: # TREE_DIR comes from the CLI option --tree, only avialable for adhoc self.tree = unfrackpath(TREE_DIR) else: self.tree = self.get_option('directory')
def _update_connection_state(self): ''' Reconstruct the connection socket_path and check if it exists If the socket path exists then the connection is active and set both the _socket_path value to the path and the _connected value to True. If the socket path doesn't exist, leave the socket path value to None and the _connected value to False ''' ssh = connection_loader.get('ssh', class_only=True) control_path = ssh._create_control_path( self._play_context.remote_addr, self._play_context.port, self._play_context.remote_user, self._play_context.connection, self._assible_playbook_pid ) tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR) socket_path = unfrackpath(control_path % dict(directory=tmp_path)) if os.path.exists(socket_path): self._connected = True self._socket_path = socket_path
def path_dwim(self, given): ''' make relative paths work like folks expect. ''' given = unquote(given) given = to_text(given, errors='surrogate_or_strict') if given.startswith(to_text(os.path.sep)) or given.startswith(u'~'): path = given else: basedir = to_text(self._basedir, errors='surrogate_or_strict') path = os.path.join(basedir, given) return unfrackpath(path, follow=False)
def run(self): super(ConfigCLI, self).run() if context.CLIARGS['config_file']: self.config_file = unfrackpath(context.CLIARGS['config_file'], follow=False) b_config = to_bytes(self.config_file) if os.path.exists(b_config) and os.access(b_config, os.R_OK): self.config = ConfigManager(self.config_file) else: raise AssibleOptionsError( 'The provided configuration file is missing or not accessible: %s' % to_native(self.config_file)) else: self.config = ConfigManager() self.config_file = find_ini_config_file() if self.config_file: try: if not os.path.exists(self.config_file): raise AssibleOptionsError( "%s does not exist or is not accessible" % (self.config_file)) elif not os.path.isfile(self.config_file): raise AssibleOptionsError("%s is not a valid file" % (self.config_file)) os.environ['ASSIBLE_CONFIG'] = to_native(self.config_file) except Exception: if context.CLIARGS['action'] in ['view']: raise elif context.CLIARGS['action'] in ['edit', 'update']: display.warning( "File does not exist, used empty file: %s" % self.config_file) elif context.CLIARGS['action'] == 'view': raise AssibleError('Invalid or no config file was supplied') context.CLIARGS['func']()
def parse_sources(self, cache=False): ''' iterate over inventory sources and parse each one to populate it''' parsed = False # allow for multiple inventory parsing for source in self._sources: if source: if ',' not in source: source = unfrackpath(source, follow=False) parse = self.parse_source(source, cache=cache) if parse and not parsed: parsed = True if parsed: # do post processing self._inventory.reconcile_inventory() else: if C.INVENTORY_UNPARSED_IS_FAILED: raise AssibleError( "No inventory was parsed, please check your configuration and options." ) else: display.warning( "No inventory was parsed, only implicit localhost is available" ) for group in self.groups.values(): group.vars = combine_vars( group.vars, get_vars_from_inventory_sources(self._loader, self._sources, [group], 'inventory')) for host in self.hosts.values(): host.vars = combine_vars( host.vars, get_vars_from_inventory_sources(self._loader, self._sources, [host], 'inventory'))
def _load_role_path(self, role_name): ''' the 'role', as specified in the ds (or as a bare string), can either be a simple name or a full path. If it is a full path, we use the basename as the role name, otherwise we take the name as-given and append it to the default role path ''' # create a templar class to template the dependency names, in # case they contain variables if self._variable_manager is not None: all_vars = self._variable_manager.get_vars(play=self._play) else: all_vars = dict() templar = Templar(loader=self._loader, variables=all_vars) role_name = templar.template(role_name) role_tuple = None # try to load as a collection-based role first if self._collection_list or AssibleCollectionRef.is_valid_fqcr( role_name): role_tuple = _get_collection_role_path(role_name, self._collection_list) if role_tuple: # we found it, stash collection data and return the name/path tuple self._role_collection = role_tuple[2] return role_tuple[0:2] # We didn't find a collection role, look in defined role paths # FUTURE: refactor this to be callable from internal so we can properly order # assible.legacy searches with the collections keyword # we always start the search for roles in the base directory of the playbook role_search_paths = [ os.path.join(self._loader.get_basedir(), u'roles'), ] # also search in the configured roles path if C.DEFAULT_ROLES_PATH: role_search_paths.extend(C.DEFAULT_ROLES_PATH) # next, append the roles basedir, if it was set, so we can # search relative to that directory for dependent roles if self._role_basedir: role_search_paths.append(self._role_basedir) # finally as a last resort we look in the current basedir as set # in the loader (which should be the playbook dir itself) but without # the roles/ dir appended role_search_paths.append(self._loader.get_basedir()) # now iterate through the possible paths and return the first one we find for path in role_search_paths: path = templar.template(path) role_path = unfrackpath(os.path.join(path, role_name)) if self._loader.path_exists(role_path): return (role_name, role_path) # if not found elsewhere try to extract path from name role_path = unfrackpath(role_name) if self._loader.path_exists(role_path): role_name = os.path.basename(role_name) return (role_name, role_path) searches = (self._collection_list or []) + role_search_paths raise AssibleError("the role '%s' was not found in %s" % (role_name, ":".join(searches)), obj=self._ds)
def post_process_args(self, options): """Process the command line args Subclasses need to implement this method. This method validates and transforms the command line arguments. It can be used to check whether conflicting values were given, whether filenames exist, etc. An implementation will look something like this:: def post_process_args(self, options): options = super(MyCLI, self).post_process_args(options) if options.addition and options.subtraction: raise AssibleOptionsError('Only one of --addition and --subtraction can be specified') if isinstance(options.listofhosts, string_types): options.listofhosts = string_types.split(',') return options """ # process tags if hasattr(options, 'tags') and not options.tags: # optparse defaults does not do what's expected # More specifically, we want `--tags` to be additive. So we cannot # simply change C.TAGS_RUN's default to ["all"] because then passing # --tags foo would cause us to have ['all', 'foo'] options.tags = ['all'] if hasattr(options, 'tags') and options.tags: tags = set() for tag_set in options.tags: for tag in tag_set.split(u','): tags.add(tag.strip()) options.tags = list(tags) # process skip_tags if hasattr(options, 'skip_tags') and options.skip_tags: skip_tags = set() for tag_set in options.skip_tags: for tag in tag_set.split(u','): skip_tags.add(tag.strip()) options.skip_tags = list(skip_tags) # process inventory options except for CLIs that require their own processing if hasattr(options, 'inventory') and not self.SKIP_INVENTORY_DEFAULTS: if options.inventory: # should always be list if isinstance(options.inventory, string_types): options.inventory = [options.inventory] # Ensure full paths when needed options.inventory = [ unfrackpath(opt, follow=False) if ',' not in opt else opt for opt in options.inventory ] else: options.inventory = C.DEFAULT_HOST_LIST # Dup args set on the root parser and sub parsers results in the root parser ignoring the args. e.g. doing # 'assible-galaxy -vvv init' has no verbosity set but 'assible-galaxy init -vvv' sets a level of 3. To preserve # back compat with pre-argparse changes we manually scan and set verbosity based on the argv values. if self.parser.prog in ['assible-galaxy', 'assible-vault' ] and not options.verbosity: verbosity_arg = next( iter([arg for arg in self.args if arg.startswith('-v')]), None) if verbosity_arg: display.deprecated( "Setting verbosity before the arg sub command is deprecated, set the verbosity " "after the sub command", "2.13", collection_name='assible.builtin') options.verbosity = verbosity_arg.count('v') return options
def find_ini_config_file(warnings=None): ''' Load INI Config File order(first found is used): ENV, CWD, HOME, /etc/assible ''' # FIXME: eventually deprecate ini configs if warnings is None: # Note: In this case, warnings does nothing warnings = set() # A value that can never be a valid path so that we can tell if ASSIBLE_CONFIG was set later # We can't use None because we could set path to None. SENTINEL = object potential_paths = [] # Environment setting path_from_env = os.getenv("ASSIBLE_CONFIG", SENTINEL) if path_from_env is not SENTINEL: path_from_env = unfrackpath(path_from_env, follow=False) if os.path.isdir(to_bytes(path_from_env)): path_from_env = os.path.join(path_from_env, "assible.cfg") potential_paths.append(path_from_env) # Current working directory warn_cmd_public = False try: cwd = os.getcwd() perms = os.stat(cwd) cwd_cfg = os.path.join(cwd, "assible.cfg") if perms.st_mode & stat.S_IWOTH: # Working directory is world writable so we'll skip it. # Still have to look for a file here, though, so that we know if we have to warn if os.path.exists(cwd_cfg): warn_cmd_public = True else: potential_paths.append(to_text(cwd_cfg, errors='surrogate_or_strict')) except OSError: # If we can't access cwd, we'll simply skip it as a possible config source pass # Per user location potential_paths.append(unfrackpath("~/.assible.cfg", follow=False)) # System location potential_paths.append("/etc/assible/assible.cfg") for path in potential_paths: b_path = to_bytes(path) if os.path.exists(b_path) and os.access(b_path, os.R_OK): break else: path = None # Emit a warning if all the following are true: # * We did not use a config from ASSIBLE_CONFIG # * There's an assible.cfg in the current working directory that we skipped if path_from_env != path and warn_cmd_public: warnings.add(u"Assible is being run in a world writable directory (%s)," u" ignoring it as an assible.cfg source." u" For more information see" u" https://docs.assible.com/assible/devel/reference_appendices/config.html#cfg-in-world-writable-dir" % to_text(cwd)) return path
def resolve_path(path, basedir=None): ''' resolve relative or 'variable' paths ''' if '{{CWD}}' in path: # allow users to force CWD using 'magic' {{CWD}} path = path.replace('{{CWD}}', os.getcwd()) return unfrackpath(path, follow=False, basedir=basedir)
def path_dwim_relative_stack(self, paths, dirname, source, is_role=False): ''' find one file in first path in stack taking roles into account and adding play basedir as fallback :arg paths: A list of text strings which are the paths to look for the filename in. :arg dirname: A text string representing a directory. The directory is prepended to the source to form the path to search for. :arg source: A text string which is the filename to search for :rtype: A text string :returns: An absolute path to the filename ``source`` if found :raises: An AssibleFileNotFound Exception if the file is found to exist in the search paths ''' b_dirname = to_bytes(dirname, errors='surrogate_or_strict') b_source = to_bytes(source, errors='surrogate_or_strict') result = None search = [] if source is None: display.warning( 'Invalid request to find a file that matches a "null" value') elif source and (source.startswith('~') or source.startswith(os.path.sep)): # path is absolute, no relative needed, check existence and return source test_path = unfrackpath(b_source, follow=False) if os.path.exists(to_bytes(test_path, errors='surrogate_or_strict')): result = test_path else: display.debug(u'evaluation_path:\n\t%s' % '\n\t'.join(paths)) for path in paths: upath = unfrackpath(path, follow=False) b_upath = to_bytes(upath, errors='surrogate_or_strict') b_pb_base_dir = os.path.dirname(b_upath) # if path is in role and 'tasks' not there already, add it into the search if (is_role or self._is_role(path) ) and b_pb_base_dir.endswith(b'/tasks'): search.append( os.path.join(os.path.dirname(b_pb_base_dir), b_dirname, b_source)) search.append(os.path.join(b_pb_base_dir, b_source)) else: # don't add dirname if user already is using it in source if b_source.split(b'/')[0] != dirname: search.append( os.path.join(b_upath, b_dirname, b_source)) search.append(os.path.join(b_upath, b_source)) # always append basedir as last resort # don't add dirname if user already is using it in source if b_source.split(b'/')[0] != dirname: search.append( os.path.join( to_bytes(self.get_basedir(), errors='surrogate_or_strict'), b_dirname, b_source)) search.append( os.path.join( to_bytes(self.get_basedir(), errors='surrogate_or_strict'), b_source)) display.debug(u'search_path:\n\t%s' % to_text(b'\n\t'.join(search))) for b_candidate in search: display.vvvvv(u'looking for "%s" at "%s"' % (source, to_text(b_candidate))) if os.path.exists(b_candidate): result = to_text(b_candidate) break if result is None: raise AssibleFileNotFound(file_name=source, paths=[to_native(p) for p in search]) return result
def path_dwim_relative(self, path, dirname, source, is_role=False): ''' find one file in either a role or playbook dir with or without explicitly named dirname subdirs Used in action plugins and lookups to find supplemental files that could be in either place. ''' search = [] source = to_text(source, errors='surrogate_or_strict') # I have full path, nothing else needs to be looked at if source.startswith(to_text(os.path.sep)) or source.startswith(u'~'): search.append(unfrackpath(source, follow=False)) else: # base role/play path + templates/files/vars + relative filename search.append(os.path.join(path, dirname, source)) basedir = unfrackpath(path, follow=False) # not told if role, but detect if it is a role and if so make sure you get correct base path if not is_role: is_role = self._is_role(path) if is_role and RE_TASKS.search(path): basedir = unfrackpath(os.path.dirname(path), follow=False) cur_basedir = self._basedir self.set_basedir(basedir) # resolved base role/play path + templates/files/vars + relative filename search.append( unfrackpath(os.path.join(basedir, dirname, source), follow=False)) self.set_basedir(cur_basedir) if is_role and not source.endswith(dirname): # look in role's tasks dir w/o dirname search.append( unfrackpath(os.path.join(basedir, 'tasks', source), follow=False)) # try to create absolute path for loader basedir + templates/files/vars + filename search.append( unfrackpath(os.path.join(dirname, source), follow=False)) # try to create absolute path for loader basedir search.append( unfrackpath(os.path.join(basedir, source), follow=False)) # try to create absolute path for dirname + filename search.append(self.path_dwim(os.path.join(dirname, source))) # try to create absolute path for filename search.append(self.path_dwim(source)) for candidate in search: if os.path.exists(to_bytes(candidate, errors='surrogate_or_strict')): break return candidate
def main(): """ Called to initiate the connect to the remote device """ rc = 0 result = {} messages = list() socket_path = None # Need stdin as a byte stream if PY3: stdin = sys.stdin.buffer else: stdin = sys.stdin # Note: update the below log capture code after Display.display() is refactored. saved_stdout = sys.stdout sys.stdout = StringIO() try: # read the play context data via stdin, which means depickling it vars_data = read_stream(stdin) init_data = read_stream(stdin) if PY3: pc_data = cPickle.loads(init_data, encoding='bytes') variables = cPickle.loads(vars_data, encoding='bytes') else: pc_data = cPickle.loads(init_data) variables = cPickle.loads(vars_data) play_context = PlayContext() play_context.deserialize(pc_data) display.verbosity = play_context.verbosity except Exception as e: rc = 1 result.update({ 'error': to_text(e), 'exception': traceback.format_exc() }) if rc == 0: ssh = connection_loader.get('ssh', class_only=True) assible_playbook_pid = sys.argv[1] task_uuid = sys.argv[2] cp = ssh._create_control_path(play_context.remote_addr, play_context.port, play_context.remote_user, play_context.connection, assible_playbook_pid) # create the persistent connection dir if need be and create the paths # which we will be using later tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR) makedirs_safe(tmp_path) socket_path = unfrackpath(cp % dict(directory=tmp_path)) lock_path = unfrackpath("%s/.assible_pc_lock_%s" % os.path.split(socket_path)) with file_lock(lock_path): if not os.path.exists(socket_path): messages.append(('vvvv', 'local domain socket does not exist, starting it')) original_path = os.getcwd() r, w = os.pipe() pid = fork_process() if pid == 0: try: os.close(r) wfd = os.fdopen(w, 'w') process = ConnectionProcess(wfd, play_context, socket_path, original_path, task_uuid, assible_playbook_pid) process.start(variables) except Exception: messages.append(('error', traceback.format_exc())) rc = 1 if rc == 0: process.run() else: process.shutdown() sys.exit(rc) else: os.close(w) rfd = os.fdopen(r, 'r') data = json.loads(rfd.read(), cls=AssibleJSONDecoder) messages.extend(data.pop('messages')) result.update(data) else: messages.append(('vvvv', 'found existing local domain socket, using it!')) conn = Connection(socket_path) conn.set_options(var_options=variables) pc_data = to_text(init_data) try: conn.update_play_context(pc_data) conn.set_check_prompt(task_uuid) except Exception as exc: # Only network_cli has update_play context and set_check_prompt, so missing this is # not fatal e.g. netconf if isinstance(exc, ConnectionError) and getattr(exc, 'code', None) == -32601: pass else: result.update({ 'error': to_text(exc), 'exception': traceback.format_exc() }) if os.path.exists(socket_path): messages.extend(Connection(socket_path).pop_messages()) messages.append(('vvvv', sys.stdout.getvalue())) result.update({ 'messages': messages, 'socket_path': socket_path }) sys.stdout = saved_stdout if 'exception' in result: rc = 1 sys.stderr.write(json.dumps(result, cls=AssibleJSONEncoder)) else: rc = 0 sys.stdout.write(json.dumps(result, cls=AssibleJSONEncoder)) sys.exit(rc)