Beispiel #1
0
def get_job_filter(data=None):
    """Helper function to return a the filterable list of OS's based on platform.slug and a specific custom value."""
    if not data:
        data = {}
    query = {}
    for field in FIELDS:
        if data.get(field):
            query[f"{field}_id"] = data[field].values_list("pk", flat=True)
    # Handle case where object is from single device run all.
    if data.get("device") and isinstance(data["device"], Device):
        query.update({"id": [str(data["device"].pk)]})
    elif data.get("device"):
        query.update({"id": data["device"].values_list("pk", flat=True)})

    base_qs = models.GoldenConfigSetting.objects.first().get_queryset()
    if base_qs.count() == 0:
        raise NornirNautobotException(
            "The base queryset didn't find any devices. Please check the Golden Config Setting scope."
        )
    devices_filtered = DeviceFilterSet(data=query, queryset=base_qs)
    if devices_filtered.qs.count() == 0:
        raise NornirNautobotException(
            "The provided job parameters didn't match any devices detected by the Golden Config scope. Please check the scope defined within Golden Config Settings or select the correct job parameters to correctly match devices."
        )
    devices_no_platform = devices_filtered.qs.filter(platform__isnull=True)
    if devices_no_platform.count() > 0:
        raise NornirNautobotException(
            f"The following device(s) {', '.join([device.name for device in devices_no_platform])} have no platform defined. Platform is required."
        )

    return devices_filtered.qs
def verify_global_settings(logger, global_settings, attrs):
    """Helper function to verify required attributes are set before a Nornir play start."""
    for item in attrs:
        if not getattr(global_settings, item):
            logger.log_failure(
                None, f"Missing the required global setting: `{item}`.")
            raise NornirNautobotException()
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 check_jinja_template(obj, logger, template):
    """Helper function to catch Jinja based issues and raise with proper NornirException."""
    try:
        template_rendered = Template(template,
                                     undefined=StrictUndefined).render(obj=obj)
        return template_rendered
    except UndefinedError as error:
        logger.log_failure(obj,
                           f"Jinja `{template}` has an error of `{error}`.")
        raise NornirNautobotException()
    except TemplateSyntaxError as error:
        logger.log_failure(obj,
                           f"Jinja `{template}` has an error of `{error}`.")
        raise NornirNautobotException()
    except TemplateError as error:
        logger.log_failure(obj,
                           f"Jinja `{template}` has an error of `{error}`.")
        raise NornirNautobotException()
Beispiel #5
0
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)
Beispiel #6
0
def run_template(  # pylint: disable=too-many-arguments
        task: Task, logger, global_settings, nautobot_job,
        jinja_root_path) -> 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
        logger (NornirLogger): Logger to log messages to.
        global_settings (GoldenConfigSetting): The settings for GoldenConfigPlugin.
        nautobot_job (Result): The the output from the Nautobot Job instance being run.
        jinja_root_path (str): The root path to the Jinja2 intended config file.

    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_directory = get_repository_working_dir("intended", obj, logger,
                                                    global_settings)
    intended_path_template_obj = render_jinja_template(
        obj, logger, global_settings.intended_path_template)
    output_file_location = os.path.join(intended_directory,
                                        intended_path_template_obj)

    jinja_template = render_jinja_template(obj, logger,
                                           global_settings.jinja_path_template)
    status, device_data = graph_ql_query(nautobot_job.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(),
        jinja_filters=jinja_env.filters,
    )[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)
Beispiel #7
0
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)