def execute_module(self): changed = False results = [] self.client = self.get_api_client() flattened_definitions = [] for definition in self.resource_definitions: # For templates, template each definition before applying it. template = self.params.get('template') if template: # TODO: Is there a way we can get all the available vars, e.g. # via action plugin or something, to pass to Templar()? all_vars = { "test_key": "test-value", } loader = DataLoader() definition = Templar(loader=loader, variables=all_vars).template(definition) kind = definition.get('kind', self.kind) api_version = definition.get('apiVersion', self.api_version) if kind.endswith('List'): resource = self.find_resource(kind, api_version, fail=False) flattened_definitions.extend( self.flatten_list_kind(resource, definition)) else: resource = self.find_resource(kind, api_version, fail=True) flattened_definitions.append((resource, definition)) for (resource, definition) in flattened_definitions: kind = definition.get('kind', self.kind) api_version = definition.get('apiVersion', self.api_version) definition = self.set_defaults(resource, definition) self.warnings = [] if self.params['validate'] is not None: self.warnings = self.validate(definition) result = self.perform_action(resource, definition) result['warnings'] = self.warnings changed = changed or result['changed'] results.append(result) if len(results) == 1: self.exit_json(**results[0]) self.exit_json(**{'changed': changed, 'result': {'results': results}})
class ActionModule(ActionBase): """ action module """ def __init__(self, *args, **kwargs): super(ActionModule, self).__init__(*args, **kwargs) self._cmd_dict = None self._errors = [] self._facts = {} self._filters = {} self._ignore_parser_errors = False self._network_os = None self._commands = None self._module_name = None self._engine = None self._pyats_device = None self._result = None self._result_details = [] self._templar_filters = Templar(loader=None).environment.filters def _add_facts(self, parsed): if isinstance(parsed, list): for chunk in parsed: self._facts = dict_merge(self._facts, chunk) elif isinstance(parsed, dict): for k, val in parsed.items(): if k in self._facts: # pylint: disable=C0123 if type(self._facts[k]) != type(val): message = ("Cannot merge '{}' and '{}'" " for facts key '{fkey}'. Ensure" " all values for '{fkey}' are of" " the same type".format(type( self._facts[k]).__name__, type(val).__name__, fkey=k)) raise AnsibleError(message) self._facts = dict_merge(self._facts, parsed) def _check_argspec(self): # pylint: disable=W0212 basic._ANSIBLE_ARGS = to_bytes( json.dumps({'ANSIBLE_MODULE_ARGS': self._task.args})) # pylint: enable=W0212 spec = {k: v for k, v in ARGSPEC.items() if k in VALID_MODULE_KWARGS} basic.AnsibleModule.fail_json = self._fail_json basic.AnsibleModule(**spec) def _check_engine(self): if self._engine == "pyats": if not PY3: self._errors.append("Genie requires Python 3") if not HAS_GENIE: self._errors.append("Genie not found. Run 'pip install genie'") if not HAS_PYATS: self._errors.append("pyATS not found. Run 'pip install pyats'") elif self._engine == "native_xml": if not HAS_XMLTODICT: self._errors.append("xmltodict not found." " Run 'pip install xmltodict'") def _check_network_os(self): if not self._network_os: self._errors.append("'network_os' must be provided or" "ansible_network_os set for this host") def _check_commands_against_pyats(self): network_os = self._task.args.get('network_os') or self._network_os self._pyats_device = Device("uut", os=network_os) self._pyats_device.custom.setdefault("abstraction", {})["order"] = ["os"] self._pyats_device.cli = AttrDict({"execute": None}) for command in self._commands: try: get_parser(command['command'], self._pyats_device) except Exception: # pylint: disable=W0703 self._errors.append("PYATS: Unable to find parser for command " "'{}' for {}".format( command['command'], network_os)) self._check_for_errors() def _check_for_errors(self): if self._errors: raise AnsibleError(". ".join(self._errors)) def _check_module_name(self): if self._module_name not in self._shared_loader_obj.module_loader: self._errors.append("{} is not supported".format(self._network_os)) self._check_for_errors() def _check_transforms(self): for command in self._commands: for transform in command.get('transform', []): if 'name' not in transform: self._errors.append( "Transform '{}' missing `name` in command '{}'".format( transform['name'], command['command'])) continue if not self._filters.get(transform['name']): self._errors.append( "Transform '{}' not found in command '{}'".format( transform['name'], command['command'])) def _fail_json(self, msg): msg = msg.replace('(basic.py)', self._task.action) raise AnsibleModuleError(msg) def _load_filters(self): filter_loader = getattr(self._shared_loader_obj, 'filter_loader') for fpl in filter_loader.all(): self._filters.update(fpl.filters()) for command in self._commands: for transform in command.get('transform', []): if 'name' not in transform: self._errors.append( "Transform '{}' missing `name` in command '{}'".format( transform['name'], command['command'])) continue if hasattr(self._task, 'collections'): for collection in self._task.collections or []: full_name = "{}.{}".format(collection, transform['name']) filterfn = self._templar_filters.get(full_name) if filterfn: self._filters[transform['name']] = filterfn break if transform['name'] not in self._filters: full_name = "{}.{}".format('nmake.jetpack', transform['name']) filterfn = self._templar_filters.get(full_name) if filterfn: self._filters[transform['name']] = filterfn def _set_send_commands(self): if self._engine == 'native_json': if self._network_os == "junos": append_json = " | display json" else: append_json = " | json" for command in self._commands: command['command'] += append_json elif self._engine == 'native_xml': append_xml = " | xmlout" for command in self._commands: command['command'] += append_xml def _parse_stdout(self): for command in self._commands: stdout = self._cmd_dict.get(command['command']) entry = {"command": command['command']} try: if self._engine == 'pyats': parsed = self._pyats_device.parse(command['command'], output=stdout) elif self._engine == 'native_json': if isinstance(stdout, str): parsed = {} else: parsed = stdout elif self._engine == 'native_xml': if not stdout: parsed = {} else: splitted = stdout.splitlines() if splitted[-1] == ']]>]]>': stdout = '\n'.join(splitted[:-1]) parsed = xmltodict.parse(stdout) except Exception as err: # pylint: disable=W0703 msg = ("{}: Unable to parse output for command '{}' for {}". format(self._engine.upper(), command['command'], self._network_os)) if self._ignore_parser_errors: display.warning(msg) parsed = {} else: self._errors.append(msg) self._check_for_errors() parsed = self._run_transforms(command, parsed) entry['parsed'] = parsed self._result_details.append(entry) if command.get('set_fact'): self._add_facts(parsed) def _run_commands(self): commands = list(set(command['command'] for command in self._commands)) new_module_args = {"commands": commands} res = self._execute_module(module_name=self._module_name, module_args=new_module_args, task_vars={}, tmp=None) if res.get('failed'): raise AnsibleError("Failure while running command on" " device: {}".format(res['msg'])) self._cmd_dict = { c: res['stdout'][idx] for idx, c in enumerate(commands) } def _run_transforms(self, command, parsed): for transform in command.get('transform', []): filterfn = self._filters.get(transform['name']) del transform['name'] parsed = filterfn(parsed, **transform) return parsed def _set_vars(self, task_vars): self._network_os = task_vars.get('ansible_network_os') self._commands = self._task.args.get('commands') self._module_name = '{}_command'.format(self._network_os) self._engine = self._task.args.get('engine') self._ignore_parser_errors = self._task.args.get( 'ignore_parser_errors') def run(self, tmp=None, task_vars=None): # pylint: disable=W0212 self._result = super(ActionModule, self).run(tmp, task_vars) self._check_argspec() self._set_vars(task_vars) self._check_engine() self._check_network_os() self._check_for_errors() self._load_filters() self._check_transforms() self._check_for_errors() self._check_module_name() if self._engine == 'pyats': self._check_commands_against_pyats() self._check_for_errors() self._set_send_commands() self._run_commands() self._parse_stdout() current_facts = task_vars.get('vars', {}).get('ansible_facts', {}) new_facts = dict_merge(current_facts, self._facts) self._result.update({ 'ansible_facts': new_facts, 'details': self._result_details }) return self._result