def prepare_ansible_config_command(self, action, config_file=None, only_changed=None): if action not in AnsibleCfgConfig._supported_actions: raise ConfigurationError( "Invalid action {0}, valid value is one of either {1}".format( action, ", ".join(AnsibleCfgConfig._supported_actions))) if action != 'dump' and only_changed: raise ConfigurationError( "only_changed is applicable for action 'dump'") self._prepare_env(runner_mode=self.runner_mode) self.cmdline_args = [] self.cmdline_args.append(action) if config_file: self.cmdline_args.extend(['-c', config_file]) if only_changed: self.cmdline_args.append('--only-changed') self.command = [self._ansible_config_exec_path] + self.cmdline_args self._handle_command_wrap(self.execution_mode, self.cmdline_args)
def get_contents(self, path): ''' Loads the contents of the file specified by path Args: path (string): The relative or absolute path to the file to be loaded. If the path is relative, then it is combined with the base_path to generate a full path string Returns: string: The contents of the file as a string Raises: ConfigurationError: If the file cannot be loaded ''' try: if not os.path.exists(path): raise ConfigurationError('specified path does not exist %s' % path) with open(path) as f: data = f.read() return data except (IOError, OSError) as exc: raise ConfigurationError('error trying to load file contents: %s' % exc)
def prepare_plugin_docs_command(self, plugin_names, plugin_type=None, response_format=None, snippet=False, playbook_dir=None, module_path=None): if response_format and response_format not in DocConfig._supported_response_formats: raise ConfigurationError("Invalid response_format {0}, valid value is one of either {1}".format(response_format, ", ".join(DocConfig._supported_response_formats))) if not isinstance(plugin_names, list): raise ConfigurationError("plugin_names should be of type list, instead received {0} of type {1}".format(plugin_names, type(plugin_names))) self._prepare_env(runner_mode=self.runner_mode) self.command = ['ansible-doc'] self.cmdline_args = [] if response_format == 'json': self.cmdline_args.append('-j') if snippet: self.cmdline_args.append('-s') if plugin_type: self.cmdline_args.extend(['-t', plugin_type]) if playbook_dir: self.cmdline_args.extend(['--playbook-dir', playbook_dir]) if module_path: self.cmdline_args.extend(['-M', module_path]) self.cmdline_args.append(" ".join(plugin_names)) self.command = ['ansible-doc'] + self.cmdline_args self._handle_command_wrap(self.execution_mode, self.cmdline_args)
def prepare(self): if self.private_data_dir is None: raise ConfigurationError("Runner Base Directory is not defined") if self.playbook is None: # TODO: ad-hoc mode, module and args raise ConfigurationError("Runner playbook is not defined") if not os.path.exists(self.artifact_dir): os.makedirs(self.artifact_dir) self.prepare_inventory() self.prepare_env() self.prepare_command() # write the SSH key data into a fifo read by ssh-agent if self.ssh_key_data: self.ssh_key_path = os.path.join(self.artifact_dir, 'ssh_key_data') self.ssh_auth_sock = os.path.join(self.artifact_dir, 'ssh_auth.sock') self.open_fifo_write(self.ssh_key_path, self.ssh_key_data) self.command = self.wrap_args_with_ssh_agent(self.command, self.ssh_key_path, self.ssh_auth_sock) # Use local callback directory callback_dir = os.getenv('AWX_LIB_DIRECTORY') if callback_dir is None: callback_dir = os.path.join(os.path.split(os.path.abspath(__file__))[0], "callbacks") self.env['ANSIBLE_CALLBACK_PLUGINS'] = callback_dir if 'AD_HOC_COMMAND_ID' in self.env: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' else: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display' self.env['ANSIBLE_RETRY_FILES_ENABLED'] = 'False' self.env['ANSIBLE_HOST_KEY_CHECKING'] = 'False' self.env['AWX_ISOLATED_DATA_DIR'] = self.artifact_dir self.env['PYTHONPATH'] = self.env.get('PYTHONPATH', '') + callback_dir + ':'
def prepare_inventory_command(self, action, inventories, response_format=None, host=None, playbook_dir=None, vault_ids=None, vault_password_file=None): if action not in InventoryConfig._supported_actions: raise ConfigurationError( "Invalid action {0}, valid value is one of either {1}".format( action, ", ".join(InventoryConfig._supported_actions))) if response_format and response_format not in InventoryConfig._supported_response_formats: raise ConfigurationError( "Invalid response_format {0}, valid value is one of " "either {1}".format( response_format, ", ".join(InventoryConfig._supported_response_formats))) if not isinstance(inventories, list): raise ConfigurationError( "inventories should be of type list, instead received {0} of type {1}" .format(inventories, type(inventories))) if action == "host" and host is None: raise ConfigurationError( "Value of host parameter is required when action in 'host'") if action == "graph" and response_format and response_format != 'json': raise ConfigurationError( "'graph' action supports only 'json' response format") self._prepare_env(runner_mode=self.runner_mode) self.cmdline_args = [] self.cmdline_args.append('--{0}'.format(action)) if action == 'host': self.cmdline_args.append(host) for inv in inventories: self.cmdline_args.extend(['-i', inv]) if response_format in ['yaml', 'toml']: self.cmdline_args.append('--{0}'.format(response_format)) if playbook_dir: self.cmdline_args.extend(['--playbook-dir', playbook_dir]) if vault_ids: self.cmdline_args.extend(['--vault-id', vault_ids]) if vault_password_file: self.cmdline_args.extend( ['--vault-password-file', vault_password_file]) self.command = ['ansible-inventory'] + self.cmdline_args self._handle_command_wrap(self.execution_mode, self.cmdline_args)
def prepare(self): """ Performs basic checks and then properly invokes - prepare_inventory - prepare_env - prepare_command It's also responsible for wrapping the command with the proper ssh agent invocation and setting early ANSIBLE_ environment variables. """ # ansible_path = find_executable('ansible') # if ansible_path is None or not os.access(ansible_path, os.X_OK): # raise ConfigurationError("Ansible not found. Make sure that it is installed.") if self.private_data_dir is None: raise ConfigurationError("Runner Base Directory is not defined") if self.module is None and self.playbook is None: # TODO: ad-hoc mode, module and args raise ConfigurationError( "Runner playbook or module is not defined") if self.module and self.playbook: raise ConfigurationError( "Only one of playbook and module options are allowed") if not os.path.exists(self.artifact_dir): os.makedirs(self.artifact_dir) self.prepare_inventory() self.prepare_env() self.prepare_command() # write the SSH key data into a fifo read by ssh-agent if self.ssh_key_data: self.ssh_key_path = os.path.join(self.artifact_dir, 'ssh_key_data') self.open_fifo_write(self.ssh_key_path, self.ssh_key_data) self.command = self.wrap_args_with_ssh_agent( self.command, self.ssh_key_path) # Use local callback directory callback_dir = os.getenv('AWX_LIB_DIRECTORY') if callback_dir is None: callback_dir = os.path.join( os.path.split(os.path.abspath(__file__))[0], "callbacks") python_path = os.getenv('PYTHONPATH', '') if python_path: python_path += ":" self.env['ANSIBLE_CALLBACK_PLUGINS'] = callback_dir if 'AD_HOC_COMMAND_ID' in self.env: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' else: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display' self.env['ANSIBLE_RETRY_FILES_ENABLED'] = 'False' self.env['ANSIBLE_HOST_KEY_CHECKING'] = 'False' self.env['AWX_ISOLATED_DATA_DIR'] = self.artifact_dir self.env['PYTHONPATH'] = python_path + callback_dir + ':' if self.roles_path: self.env['ANSIBLE_ROLES_PATH'] = ':'.join(self.roles_path) if self.process_isolation: self.command = self.wrap_args_with_process_isolation(self.command)
def load_file(self, path, objtype=None, encoding='utf-8'): ''' Load the file specified by path This method will first try to load the file contents from cache and if there is a cache miss, it will load the contents from disk Args: path (string): The full or relative path to the file to be loaded encoding (string): The file contents text encoding objtype (object): The object type of the file contents. This is used to type check the deserialized content against the contents loaded from disk. Ignore serializing if objtype is string_types Returns: object: The deserialized file contents which could be either a string object or a dict object Raises: ConfigurationError: ''' path = self.abspath(path) debug('file path is %s' % path) if path in self._cache: return self._cache[path] try: debug('cache miss, attempting to load file from disk: %s' % path) contents = parsed_data = self.get_contents(path) if encoding: parsed_data = contents.encode(encoding) except ConfigurationError as exc: debug(exc) raise except UnicodeEncodeError: raise ConfigurationError('unable to encode file contents') if objtype is not string_types: for deserializer in (self._load_json, self._load_yaml): parsed_data = deserializer(contents) if parsed_data: break if objtype and not isinstance(parsed_data, objtype): debug('specified file %s is not of type %s' % (path, objtype)) raise ConfigurationError( 'invalid file serialization type for contents') self._cache[path] = parsed_data return parsed_data
def prepare(self): """ Performs basic checks and then properly invokes - prepare_inventory - prepare_env - prepare_command It's also responsible for wrapping the command with the proper ssh agent invocation and setting early ANSIBLE_ environment variables. """ # ansible_path = find_executable('ansible') # if ansible_path is None or not os.access(ansible_path, os.X_OK): # raise ConfigurationError("Ansible not found. Make sure that it is installed.") if self.private_data_dir is None: raise ConfigurationError("Runner Base Directory is not defined") if self.module and self.playbook: raise ConfigurationError( "Only one of playbook and module options are allowed") if not os.path.exists(self.artifact_dir): os.makedirs(self.artifact_dir, mode=0o700) if self.sandboxed and self.directory_isolation_path is not None: self.directory_isolation_path = tempfile.mkdtemp( prefix='runner_di_', dir=self.directory_isolation_path) if os.path.exists(self.project_dir): output.debug( "Copying directory tree from {} to {} for working directory isolation" .format(self.project_dir, self.directory_isolation_path)) copy_tree(self.project_dir, self.directory_isolation_path, preserve_symlinks=True) self.prepare_env() self.prepare_inventory() self.prepare_command() if self.execution_mode == ExecutionMode.ANSIBLE_PLAYBOOK and self.playbook is None: raise ConfigurationError( "Runner playbook required when running ansible-playbook") elif self.execution_mode == ExecutionMode.ANSIBLE and self.module is None: raise ConfigurationError( "Runner module required when running ansible") elif self.execution_mode == ExecutionMode.NONE: raise ConfigurationError("No executable for runner to run") self._handle_command_wrap() debug('env:') for k, v in sorted(self.env.items()): debug(f' {k}: {v}') if hasattr(self, 'command') and isinstance(self.command, list): debug(f"command: {' '.join(self.command)}")
def prepare_plugin_list_command(self, list_files=None, response_format=None, plugin_type=None, playbook_dir=None, module_path=None): if response_format and response_format not in DocConfig._supported_response_formats: raise ConfigurationError("Invalid response_format {0}, valid value is one of either {1}".format(response_format, ", ".join(DocConfig._supported_response_formats))) self._prepare_env(runner_mode=self.runner_mode) self.cmdline_args = [] if list_files: self.cmdline_args.append('-F') else: self.cmdline_args.append('-l') if response_format == 'json': self.cmdline_args.append('-j') if plugin_type: self.cmdline_args.extend(['-t', plugin_type]) if playbook_dir: self.cmdline_args.extend(['--playbook-dir', playbook_dir]) if module_path: self.cmdline_args.extend(['-M', module_path]) self.command = ['ansible-doc'] + self.cmdline_args self._handle_command_wrap(self.execution_mode, self.cmdline_args)
def _ensure_path_safe_to_mount(self, path): if os.path.isfile(path): path = os.path.dirname(path) if os.path.join(path, "") in ('/', '/home/', '/usr/'): raise ConfigurationError( "When using containerized execution, cannot mount '/' or '/home' or '/usr'" )
def __init__(self, runner_mode=None, **kwargs): # runner params self.runner_mode = runner_mode if runner_mode else 'subprocess' if self.runner_mode not in ['pexpect', 'subprocess']: raise ConfigurationError("Invalid runner mode {0}, valid value is either 'pexpect' or 'subprocess'".format(self.runner_mode)) self.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS super(DocConfig, self).__init__(**kwargs)
def __init__(self, input_fd=None, output_fd=None, error_fd=None, runner_mode=None, **kwargs): # subprocess runner mode params self.input_fd = input_fd self.output_fd = output_fd self.error_fd = error_fd if runner_mode == 'pexpect' and not self.input_fd: raise ConfigurationError("input_fd is applicable only with 'subprocess' runner mode") if runner_mode and runner_mode not in ['pexpect', 'subprocess']: raise ConfigurationError("Invalid runner mode {0}, valid value is either 'pexpect' or 'subprocess'".format(runner_mode)) # runner params self.runner_mode = runner_mode self.execution_mode = BaseExecutionMode.NONE super(CommandConfig, self).__init__(**kwargs)
def _prepare_command(self): """ Determines if it is ``ansible`` command or ``generic`` command and generate the command line """ if not self.executable_cmd: raise ConfigurationError("For CommandRunner 'executable_cmd' value is required") if self.executable_cmd.split(os.pathsep)[-1].startswith('ansible'): self.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS else: self.execution_mode = BaseExecutionMode.GENERIC_COMMANDS if self.cmdline_args: self.command = [self.executable_cmd] + self.cmdline_args else: self.command = [self.executable_cmd] if self.execution_mode == BaseExecutionMode.GENERIC_COMMANDS \ and 'python' in self.executable_cmd.split(os.pathsep)[-1] and self.cmdline_args is None: raise ConfigurationError("Runner requires python filename for execution") elif self.execution_mode == BaseExecutionMode.NONE: raise ConfigurationError("No executable for runner to run")
def __init__(self, runner_mode=None, **kwargs): # runner params self.runner_mode = runner_mode if runner_mode else 'subprocess' if self.runner_mode not in ['pexpect', 'subprocess']: raise ConfigurationError( "Invalid runner mode {0}, valid value is either 'pexpect' or 'subprocess'" .format(self.runner_mode)) if kwargs.get("process_isolation"): self._ansible_doc_exec_path = "ansible-doc" else: self._ansible_doc_exec_path = get_executable_path("ansible-doc") self.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS super(DocConfig, self).__init__(**kwargs)
def prepare(self): """ Performs basic checks and then properly invokes - prepare_inventory - prepare_env - prepare_command It's also responsible for wrapping the command with the proper ssh agent invocation and setting early ANSIBLE_ environment variables. """ # ansible_path = find_executable('ansible') # if ansible_path is None or not os.access(ansible_path, os.X_OK): # raise ConfigurationError("Ansible not found. Make sure that it is installed.") if self.private_data_dir is None: raise ConfigurationError("Runner Base Directory is not defined") if self.module and self.playbook: raise ConfigurationError("Only one of playbook and module options are allowed") if not os.path.exists(self.artifact_dir): os.makedirs(self.artifact_dir, mode=0o700) if self.directory_isolation_path is not None: self.directory_isolation_path = tempfile.mkdtemp(prefix='runner_di_', dir=self.directory_isolation_path) if os.path.exists(self.project_dir): output.debug("Copying directory tree from {} to {} for working directory isolation".format(self.project_dir, self.directory_isolation_path)) copy_tree(self.project_dir, self.directory_isolation_path, preserve_symlinks=True) self.prepare_inventory() self.prepare_env() self.prepare_command() if self.execution_mode == ExecutionMode.ANSIBLE_PLAYBOOK and self.playbook is None: raise ConfigurationError("Runner playbook required when running ansible-playbook") elif self.execution_mode == ExecutionMode.ANSIBLE and self.module is None: raise ConfigurationError("Runner module required when running ansible") elif self.execution_mode == ExecutionMode.NONE: raise ConfigurationError("No executable for runner to run") # write the SSH key data into a fifo read by ssh-agent if self.ssh_key_data: self.ssh_key_path = os.path.join(self.artifact_dir, 'ssh_key_data') open_fifo_write(self.ssh_key_path, self.ssh_key_data) self.command = self.wrap_args_with_ssh_agent(self.command, self.ssh_key_path) # Use local callback directory callback_dir = self.env.get('AWX_LIB_DIRECTORY', os.getenv('AWX_LIB_DIRECTORY')) if callback_dir is None: callback_dir = os.path.join(os.path.split(os.path.abspath(__file__))[0], "callbacks") python_path = self.env.get('PYTHONPATH', os.getenv('PYTHONPATH', '')) if python_path and not python_path.endswith(':'): python_path += ':' self.env['ANSIBLE_CALLBACK_PLUGINS'] = callback_dir if 'AD_HOC_COMMAND_ID' in self.env: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' else: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display' self.env['ANSIBLE_RETRY_FILES_ENABLED'] = 'False' self.env['ANSIBLE_HOST_KEY_CHECKING'] = 'False' self.env['AWX_ISOLATED_DATA_DIR'] = self.artifact_dir self.env['PYTHONPATH'] = python_path + callback_dir if self.roles_path: self.env['ANSIBLE_ROLES_PATH'] = ':'.join(self.roles_path) if self.process_isolation: self.command = self.wrap_args_with_process_isolation(self.command) if self.fact_cache_type == 'jsonfile': self.env['ANSIBLE_CACHE_PLUGIN'] = 'jsonfile' self.env['ANSIBLE_CACHE_PLUGIN_CONNECTION'] = self.fact_cache
def _ensure_path_safe_to_mount(path): if path in ('/home', '/usr'): raise ConfigurationError("When using containerized execution, cannot mount /home or /usr")
def get_executable_path(name): exec_path = find_executable(name) if exec_path is None: raise ConfigurationError(f"{name} command not found") return exec_path
def prepare(self): """ Performs basic checks and then properly invokes - prepare_inventory - prepare_env - prepare_command It's also responsible for wrapping the command with the proper ssh agent invocation and setting early ANSIBLE_ environment variables. """ # ansible_path = find_executable('ansible') # if ansible_path is None or not os.access(ansible_path, os.X_OK): # raise ConfigurationError("Ansible not found. Make sure that it is installed.") if self.private_data_dir is None: raise ConfigurationError("Runner Base Directory is not defined") if self.module and self.playbook: raise ConfigurationError( "Only one of playbook and module options are allowed") if not os.path.exists(self.artifact_dir): os.makedirs(self.artifact_dir, mode=0o700) if self.directory_isolation_path is not None: self.directory_isolation_path = tempfile.mkdtemp( prefix='runner_di_', dir=self.directory_isolation_path) if os.path.exists(self.project_dir): output.debug( "Copying directory tree from {} to {} for working directory isolation" .format(self.project_dir, self.directory_isolation_path)) copy_tree(self.project_dir, self.directory_isolation_path, preserve_symlinks=True) self.prepare_inventory() self.prepare_env() self.prepare_command() if self.execution_mode == ExecutionMode.ANSIBLE_PLAYBOOK and self.playbook is None: raise ConfigurationError( "Runner playbook required when running ansible-playbook") elif self.execution_mode == ExecutionMode.ANSIBLE and self.module is None: raise ConfigurationError( "Runner module required when running ansible") elif self.execution_mode == ExecutionMode.NONE: raise ConfigurationError("No executable for runner to run") # write the SSH key data into a fifo read by ssh-agent if self.ssh_key_data: self.ssh_key_path = os.path.join(self.artifact_dir, 'ssh_key_data') open_fifo_write(self.ssh_key_path, self.ssh_key_data) self.command = self.wrap_args_with_ssh_agent( self.command, self.ssh_key_path) # Use local callback directory callback_dir = self.env.get('AWX_LIB_DIRECTORY', os.getenv('AWX_LIB_DIRECTORY')) if callback_dir is None: callback_dir = os.path.join( os.path.split(os.path.abspath(__file__))[0], "callbacks") python_path = self.env.get('PYTHONPATH', os.getenv('PYTHONPATH', '')) if python_path and not python_path.endswith(':'): python_path += ':' self.env['ANSIBLE_CALLBACK_PLUGINS'] = ':'.join( filter(None, (self.env.get('ANSIBLE_CALLBACK_PLUGINS'), callback_dir))) if 'AD_HOC_COMMAND_ID' in self.env: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' else: self.env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display' self.env['ANSIBLE_RETRY_FILES_ENABLED'] = 'False' if 'ANSIBLE_HOST_KEY_CHECKING' not in self.env: self.env['ANSIBLE_HOST_KEY_CHECKING'] = 'False' self.env['AWX_ISOLATED_DATA_DIR'] = self.artifact_dir if self.resource_profiling: callback_whitelist = os.environ.get('ANSIBLE_CALLBACK_WHITELIST', '').strip() self.env['ANSIBLE_CALLBACK_WHITELIST'] = ','.join( filter(None, [callback_whitelist, 'cgroup_perf_recap'])) self.env['CGROUP_CONTROL_GROUP'] = '{}/{}'.format( self.resource_profiling_base_cgroup, self.ident) if self.resource_profiling_results_dir: cgroup_output_dir = self.resource_profiling_results_dir else: cgroup_output_dir = os.path.normpath( os.path.join(self.private_data_dir, 'profiling_data')) # Create results directory if it does not exist if not os.path.isdir(cgroup_output_dir): os.mkdir(cgroup_output_dir, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) self.env['CGROUP_OUTPUT_DIR'] = cgroup_output_dir self.env['CGROUP_OUTPUT_FORMAT'] = 'json' self.env['CGROUP_CPU_POLL_INTERVAL'] = str( self.resource_profiling_cpu_poll_interval) self.env['CGROUP_MEMORY_POLL_INTERVAL'] = str( self.resource_profiling_memory_poll_interval) self.env['CGROUP_PID_POLL_INTERVAL'] = str( self.resource_profiling_pid_poll_interval) self.env['CGROUP_FILE_PER_TASK'] = 'True' self.env['CGROUP_WRITE_FILES'] = 'True' self.env['CGROUP_DISPLAY_RECAP'] = 'False' self.env['PYTHONPATH'] = python_path + callback_dir if self.roles_path: self.env['ANSIBLE_ROLES_PATH'] = ':'.join(self.roles_path) if self.process_isolation: self.command = self.wrap_args_with_process_isolation(self.command) if self.resource_profiling and self.execution_mode == ExecutionMode.ANSIBLE_PLAYBOOK: self.command = self.wrap_args_with_cgexec(self.command) if self.fact_cache_type == 'jsonfile': self.env['ANSIBLE_CACHE_PLUGIN'] = 'jsonfile' self.env['ANSIBLE_CACHE_PLUGIN_CONNECTION'] = self.fact_cache self.env["RUNNER_OMIT_EVENTS"] = str(self.omit_event_data) self.env["RUNNER_ONLY_FAILED_EVENTS"] = str( self.only_failed_event_data)