def run_template( # pylint: disable=too-many-arguments task: Task, logger, global_settings, job_result, jinja_root_path, intended_root_folder) -> Result: """Render Jinja Template. Only one template is supported, so the expectation is that that template includes all other templates. Args: task (Task): Nornir task individual object Returns: result (Result): Result from Nornir task """ obj = task.host.data["obj"] intended_obj = GoldenConfig.objects.filter(device=obj).first() if not intended_obj: intended_obj = GoldenConfig.objects.create(device=obj) intended_obj.intended_last_attempt_date = task.host.defaults.data["now"] intended_obj.save() intended_path_template_obj = check_jinja_template( obj, logger, global_settings.intended_path_template) output_file_location = os.path.join(intended_root_folder, intended_path_template_obj) jinja_template = check_jinja_template(obj, logger, global_settings.jinja_path_template) status, device_data = graph_ql_query(job_result.request, obj, global_settings.sot_agg_query) if status != 200: logger.log_failure( obj, f"The GraphQL query return a status of {str(status)} with error of {str(device_data)}" ) raise NornirNautobotException() task.host.data.update(device_data) generated_config = task.run( task=dispatcher, name="GENERATE CONFIG", method="generate_config", obj=obj, logger=logger, jinja_template=jinja_template, jinja_root_path=jinja_root_path, output_file_location=output_file_location, default_drivers_mapping=get_dispatcher(), )[1].result["config"] intended_obj.intended_last_success_date = task.host.defaults.data["now"] intended_obj.intended_config = generated_config intended_obj.save() logger.log_success(obj, "Successfully generated the intended configuration.") return Result(host=task.host, result=generated_config)
def test_check_jinja_template_exceptions_templateerror( self, template_mock): """Cause issue to cause TemplateError form Jinja2 Template.""" log_mock = Mock() with self.assertRaises(NornirNautobotException): template_mock.side_effect = TemplateError template_render = check_jinja_template("test-obj", log_mock, "template") self.assertEqual(template_render, TemplateError) template_mock.assert_called_once()
def run_backup( # pylint: disable=too-many-arguments task: Task, logger, global_settings, remove_regex_dict, replace_regex_dict, backup_root_folder) -> Result: r"""Backup configurations to disk. Args: task (Task): Nornir task individual object remove_regex_dict (dict): {'cisco_ios': ['^Building\\s+configuration.*\\n', '^Current\\s+configuration.*\\n', '^!\\s+Last\\s+configuration.*'], 'arista_eos': ['.s*']} replace_regex_dict (dict): {'cisco_ios': [{'regex_replacement': '<redacted_config>', 'regex_search': 'username\\s+\\S+\\spassword\\s+5\\s+(\\S+)\\s+role\\s+\\S+'}]} Returns: result (Result): Result from Nornir task """ obj = task.host.data["obj"] backup_obj = GoldenConfig.objects.filter(device=obj).first() if not backup_obj: backup_obj = GoldenConfig.objects.create(device=obj, ) backup_obj.backup_last_attempt_date = task.host.defaults.data["now"] backup_obj.save() backup_path_template_obj = check_jinja_template( obj, logger, global_settings.backup_path_template) backup_file = os.path.join(backup_root_folder, backup_path_template_obj) if global_settings.backup_test_connectivity is not False: task.run( task=dispatcher, name="TEST CONNECTIVITY", method="check_connectivity", obj=obj, logger=logger, default_drivers_mapping=get_dispatcher(), ) running_config = task.run( task=dispatcher, name="SAVE BACKUP CONFIGURATION TO FILE", method="get_config", obj=obj, logger=logger, backup_file=backup_file, remove_lines=remove_regex_dict.get(obj.platform.slug, []), substitute_lines=replace_regex_dict.get(obj.platform.slug, []), default_drivers_mapping=get_dispatcher(), )[1].result["config"] backup_obj.backup_last_success_date = task.host.defaults.data["now"] backup_obj.backup_config = running_config backup_obj.save() logger.log_success(obj, "Successfully backed up device.") return Result(host=task.host, result=running_config)
def run_backup(task: Task, logger, global_settings, backup_root_folder) -> Result: """Backup configurations to disk. Args: task (Task): Nornir task individual object Returns: result (Result): Result from Nornir task """ obj = task.host.data["obj"] backup_obj = GoldenConfiguration.objects.filter(device=obj).first() if not backup_obj: backup_obj = GoldenConfiguration.objects.create( device=obj, ) backup_obj.backup_last_attempt_date = task.host.defaults.data["now"] backup_obj.save() backup_path_template_obj = check_jinja_template(obj, logger, global_settings.backup_path_template) backup_file = os.path.join(backup_root_folder, backup_path_template_obj) substitute_lines = get_substitute_lines(global_settings.substitute_lines) if global_settings.backup_test_connectivity is not False: task.run( task=dispatcher, name="TEST CONNECTIVITY", method="check_connectivity", obj=obj, logger=logger, ) running_config = task.run( task=dispatcher, name="SAVE BACKUP CONFIGURATION TO FILE", method="get_config", obj=obj, logger=logger, backup_file=backup_file, remove_lines=global_settings.remove_lines.splitlines(), substitute_lines=substitute_lines, )[1].result["config"] backup_obj.backup_last_success_date = task.host.defaults.data["now"] backup_obj.backup_config = running_config backup_obj.save() logger.log_success(obj, "Successfully backed up device.") return Result(host=task.host, result=running_config)
def test_check_jinja_template_exceptions_syntaxerror(self): """Use invalid templating to cause TemplateSyntaxError from Jinja2 Template.""" log_mock = Mock() with self.assertRaises(NornirNautobotException): check_jinja_template("test-obj", log_mock, "{{ obj.fake }")
def test_check_jinja_template_exceptions_undefined(self): """Use fake obj key to cause UndefinedError from Jinja2 Template.""" log_mock = Mock() with self.assertRaises(NornirNautobotException): check_jinja_template("test-obj", log_mock, "{{ obj.fake }}")
def test_check_jinja_template_success(self): """Simple success test to return template.""" worker = check_jinja_template("obj", "logger", "fake-template-name") self.assertEqual(worker, "fake-template-name")
def run_compliance( # pylint: disable=too-many-arguments,too-many-locals task: Task, logger, global_settings, backup_root_path, intended_root_folder, features, ) -> Result: """Prepare data for compliance task. Args: task (Task): Nornir task individual object Returns: result (Result): Result from Nornir task """ obj = task.host.data["obj"] compliance_obj = GoldenConfig.objects.filter(device=obj).first() if not compliance_obj: compliance_obj = GoldenConfig.objects.create(device=obj) compliance_obj.compliance_last_attempt_date = task.host.defaults.data[ "now"] compliance_obj.save() intended_path_template_obj = check_jinja_template( obj, logger, global_settings.intended_path_template) intended_file = os.path.join(intended_root_folder, intended_path_template_obj) if not os.path.exists(intended_file): logger.log_failure( obj, f"Unable to locate intended file for device at {intended_file}") raise NornirNautobotException() backup_template = check_jinja_template( obj, logger, global_settings.backup_path_template) backup_file = os.path.join(backup_root_path, backup_template) if not os.path.exists(backup_file): logger.log_failure( obj, f"Unable to locate backup file for device at {backup_file}") raise NornirNautobotException() platform = obj.platform.slug if not features.get(platform): logger.log_failure( obj, f"There is no `user` defined feature mapping for platform slug {platform}." ) raise NornirNautobotException() if get_platform(platform) not in parser_map.keys(): logger.log_failure( obj, f"There is currently no parser support for platform slug {get_platform(platform)}." ) raise NornirNautobotException() backup_cfg = _open_file_config(backup_file) intended_cfg = _open_file_config(intended_file) # TODO: Make this atomic with compliance_obj step. for feature in features[obj.platform.slug]: # using update_or_create() method to conveniently update actual obj or create new one. ConfigCompliance.objects.update_or_create( device=obj, rule=feature["obj"], defaults={ "actual": section_config(feature, backup_cfg, get_platform(platform)), "intended": section_config(feature, intended_cfg, get_platform(platform)), "missing": "", "extra": "", }, ) compliance_obj.compliance_last_success_date = task.host.defaults.data[ "now"] compliance_obj.compliance_config = "\n".join( diff_files(backup_file, intended_file)) compliance_obj.save() logger.log_success(obj, "Successfully tested compliance.") return Result(host=task.host)
def run_compliance( # pylint: disable=too-many-arguments,too-many-locals task: Task, logger, global_settings, backup_root_path, intended_root_folder, features, ) -> Result: """Prepare data for compliance task. Args: task (Task): Nornir task individual object Returns: result (Result): Result from Nornir task """ obj = task.host.data["obj"] compliance_obj = GoldenConfiguration.objects.filter(device=obj).first() if not compliance_obj: compliance_obj = GoldenConfiguration.objects.create(device=obj) compliance_obj.compliance_last_attempt_date = task.host.defaults.data[ "now"] compliance_obj.save() intended_path_template_obj = check_jinja_template( obj, logger, global_settings.intended_path_template) intended_file = os.path.join(intended_root_folder, intended_path_template_obj) backup_template = check_jinja_template( obj, logger, global_settings.backup_path_template) backup_file = os.path.join(backup_root_path, backup_template) platform = obj.platform.slug if not features.get(platform): logger.log_failure( obj, f"There is no `user` defined feature mapping for platform slug {platform}." ) raise NornirNautobotException() if platform not in parser_map.keys(): logger.log_failure( obj, f"There is currently no parser support for platform slug {platform}." ) raise NornirNautobotException() feature_data = task.run( task=dispatcher, name="GET COMPLIANCE FOR CONFIG", method="compliance_config", obj=obj, logger=logger, backup_file=backup_file, intended_file=intended_file, features=features[platform], platform=platform, )[1].result["feature_data"] for feature, value in feature_data.items(): defaults = { "actual": null_to_empty(value["actual"]), "intended": null_to_empty(value["intended"]), "missing": null_to_empty(value["missing"]), "extra": null_to_empty(value["extra"]), "compliance": value["compliant"], "ordered": value["ordered_compliant"], } # using update_or_create() method to conveniently update actual obj or create new one. ConfigCompliance.objects.update_or_create( device=obj, feature=feature, defaults=defaults, ) compliance_obj.compliance_last_success_date = task.host.defaults.data[ "now"] compliance_obj.compliance_config = "\n".join( diff_files(backup_file, intended_file)) compliance_obj.save() logger.log_success(obj, "Successfully tested complinace.") return Result(host=task.host, result=feature_data)