Skip to content

Golden Config API Package

nautobot_golden_config.api

REST API module for nautobot_golden_config app.

serializers

API serializers for nautobot_golden_config.

ComplianceFeatureSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

ComplianceFeature Serializer.

Source code in nautobot_golden_config/api/serializers.py
class ComplianceFeatureSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """ComplianceFeature Serializer."""

    class Meta:
        """Meta attributes."""

        model = models.ComplianceFeature
        fields = "__all__"
Meta

Meta attributes.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Meta attributes."""

    model = models.ComplianceFeature
    fields = "__all__"

ComplianceRuleSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for ComplianceRule object.

Source code in nautobot_golden_config/api/serializers.py
class ComplianceRuleSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for ComplianceRule object."""

    class Meta:
        """Set Meta Data for ComplianceRule, will serialize all fields."""

        model = models.ComplianceRule
        fields = "__all__"
Meta

Set Meta Data for ComplianceRule, will serialize all fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for ComplianceRule, will serialize all fields."""

    model = models.ComplianceRule
    fields = "__all__"

ConfigComplianceSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for ConfigCompliance object.

Source code in nautobot_golden_config/api/serializers.py
class ConfigComplianceSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for ConfigCompliance object."""

    class Meta:
        """Set Meta Data for ConfigCompliance, will serialize fields."""

        model = models.ConfigCompliance
        fields = "__all__"
Meta

Set Meta Data for ConfigCompliance, will serialize fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for ConfigCompliance, will serialize fields."""

    model = models.ConfigCompliance
    fields = "__all__"

ConfigPlanSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for ConfigPlan object.

Source code in nautobot_golden_config/api/serializers.py
class ConfigPlanSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for ConfigPlan object."""

    class Meta:
        """Set Meta Data for ConfigPlan, will serialize all fields."""

        model = models.ConfigPlan
        fields = "__all__"
        read_only_fields = ["device", "plan_type", "feature", "config_set"]
Meta

Set Meta Data for ConfigPlan, will serialize all fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for ConfigPlan, will serialize all fields."""

    model = models.ConfigPlan
    fields = "__all__"
    read_only_fields = ["device", "plan_type", "feature", "config_set"]

ConfigRemoveSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for ConfigRemove object.

Source code in nautobot_golden_config/api/serializers.py
class ConfigRemoveSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for ConfigRemove object."""

    class Meta:
        """Set Meta Data for ConfigRemove, will serialize all fields."""

        model = models.ConfigRemove
        fields = "__all__"
Meta

Set Meta Data for ConfigRemove, will serialize all fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for ConfigRemove, will serialize all fields."""

    model = models.ConfigRemove
    fields = "__all__"

ConfigReplaceSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for ConfigReplace object.

Source code in nautobot_golden_config/api/serializers.py
class ConfigReplaceSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for ConfigReplace object."""

    class Meta:
        """Set Meta Data for ConfigReplace, will serialize all fields."""

        model = models.ConfigReplace
        fields = "__all__"
Meta

Set Meta Data for ConfigReplace, will serialize all fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for ConfigReplace, will serialize all fields."""

    model = models.ConfigReplace
    fields = "__all__"

ConfigToPushSerializer

Bases: DeviceSerializer

Serializer for ConfigToPush view.

Source code in nautobot_golden_config/api/serializers.py
class ConfigToPushSerializer(DeviceSerializer):  # pylint: disable=nb-sub-class-name
    """Serializer for ConfigToPush view."""

    config = serializers.SerializerMethodField()

    class Meta(DeviceSerializer.Meta):
        """Extend the Device serializer with the configuration after postprocessing."""

        fields = "__all__"
        model = Device

    def get_config(self, obj):
        """Provide the intended configuration ready after postprocessing to the config field."""
        request = self.context.get("request")
        config_details = models.GoldenConfig.objects.get(device=obj)
        return get_config_postprocessing(config_details, request)
Meta

Bases: Meta

Extend the Device serializer with the configuration after postprocessing.

Source code in nautobot_golden_config/api/serializers.py
class Meta(DeviceSerializer.Meta):
    """Extend the Device serializer with the configuration after postprocessing."""

    fields = "__all__"
    model = Device
get_config(obj)

Provide the intended configuration ready after postprocessing to the config field.

Source code in nautobot_golden_config/api/serializers.py
def get_config(self, obj):
    """Provide the intended configuration ready after postprocessing to the config field."""
    request = self.context.get("request")
    config_details = models.GoldenConfig.objects.get(device=obj)
    return get_config_postprocessing(config_details, request)

GenerateIntendedConfigSerializer

Bases: Serializer

Serializer for GenerateIntendedConfigView.

Source code in nautobot_golden_config/api/serializers.py
class GenerateIntendedConfigSerializer(serializers.Serializer):  # pylint: disable=abstract-method
    """Serializer for GenerateIntendedConfigView."""

    intended_config = serializers.CharField(read_only=True)
    intended_config_lines = serializers.ListField(read_only=True, child=serializers.CharField())
    graphql_data = serializers.JSONField(read_only=True)
    diff = serializers.CharField(read_only=True)
    diff_lines = serializers.ListField(read_only=True, child=serializers.CharField())

GitRepositoryWithBranchesSerializer

Bases: GitRepositorySerializer

Serializer for extras.GitRepository with remote branches field.

Source code in nautobot_golden_config/api/serializers.py
class GitRepositoryWithBranchesSerializer(GitRepositorySerializer):  # pylint: disable=nb-sub-class-name
    """Serializer for extras.GitRepository with remote branches field."""

    remote_branches = serializers.SerializerMethodField()

    def get_remote_branches(self, obj):
        """Return a list of branches for the GitRepository."""
        ensure_git_repository(obj)
        from_url, to_path, _ = get_repo_from_url_to_path_and_from_branch(obj)
        repo_helper = GitRepo(to_path, from_url)
        repo_helper.repo.remotes.origin.fetch()
        return [
            ref.name[7:]  # removeprefix("origin/")
            for ref in repo_helper.repo.remotes.origin.refs
            if ref.name != "origin/HEAD"
        ]

    class Meta:  # noqa: D106  # undocumented-public-nested-class
        model = GitRepository
        fields = "__all__"
get_remote_branches(obj)

Return a list of branches for the GitRepository.

Source code in nautobot_golden_config/api/serializers.py
def get_remote_branches(self, obj):
    """Return a list of branches for the GitRepository."""
    ensure_git_repository(obj)
    from_url, to_path, _ = get_repo_from_url_to_path_and_from_branch(obj)
    repo_helper = GitRepo(to_path, from_url)
    repo_helper.repo.remotes.origin.fetch()
    return [
        ref.name[7:]  # removeprefix("origin/")
        for ref in repo_helper.repo.remotes.origin.refs
        if ref.name != "origin/HEAD"
    ]

GoldenConfigSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for GoldenConfig object.

Source code in nautobot_golden_config/api/serializers.py
class GoldenConfigSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for GoldenConfig object."""

    class Meta:
        """Set Meta Data for GoldenConfig, will serialize all fields."""

        model = models.GoldenConfig
        fields = "__all__"
Meta

Set Meta Data for GoldenConfig, will serialize all fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for GoldenConfig, will serialize all fields."""

    model = models.GoldenConfig
    fields = "__all__"

GoldenConfigSettingSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for GoldenConfigSetting object.

Source code in nautobot_golden_config/api/serializers.py
class GoldenConfigSettingSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for GoldenConfigSetting object."""

    class Meta:
        """Set Meta Data for GoldenConfigSetting, will serialize all fields."""

        model = models.GoldenConfigSetting
        fields = "__all__"
Meta

Set Meta Data for GoldenConfigSetting, will serialize all fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for GoldenConfigSetting, will serialize all fields."""

    model = models.GoldenConfigSetting
    fields = "__all__"

GraphQLSerializer

Bases: Serializer

Serializer for a GraphQL object.

Source code in nautobot_golden_config/api/serializers.py
class GraphQLSerializer(serializers.Serializer):  # pylint: disable=abstract-method
    """Serializer for a GraphQL object."""

    data = serializers.JSONField(read_only=True)

RemediationSettingSerializer

Bases: NautobotModelSerializer, TaggedModelSerializerMixin

Serializer for RemediationSetting object.

Source code in nautobot_golden_config/api/serializers.py
class RemediationSettingSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
    """Serializer for RemediationSetting object."""

    class Meta:
        """Set Meta Data for RemediationSetting, will serialize all fields."""

        model = models.RemediationSetting
        fields = "__all__"
Meta

Set Meta Data for RemediationSetting, will serialize all fields.

Source code in nautobot_golden_config/api/serializers.py
class Meta:
    """Set Meta Data for RemediationSetting, will serialize all fields."""

    model = models.RemediationSetting
    fields = "__all__"

urls

Django API urlpatterns declaration for nautobot_golden_config app.

views

API views for nautobot_golden_config.

ComplianceFeatureViewSet

Bases: NautobotModelViewSet

API viewset for interacting with ComplianceFeature objects.

Source code in nautobot_golden_config/api/views.py
class ComplianceFeatureViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with ComplianceFeature objects."""

    queryset = models.ComplianceFeature.objects.all()
    serializer_class = serializers.ComplianceFeatureSerializer
    filterset_class = filters.ComplianceFeatureFilterSet

ComplianceRuleViewSet

Bases: NautobotModelViewSet

API viewset for interacting with ComplianceRule objects.

Source code in nautobot_golden_config/api/views.py
class ComplianceRuleViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with ComplianceRule objects."""

    queryset = models.ComplianceRule.objects.all()
    serializer_class = serializers.ComplianceRuleSerializer
    filterset_class = filters.ComplianceRuleFilterSet

ConfigComplianceViewSet

Bases: NautobotModelViewSet

API viewset for interacting with ConfigCompliance objects.

Source code in nautobot_golden_config/api/views.py
class ConfigComplianceViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with ConfigCompliance objects."""

    queryset = models.ConfigCompliance.objects.all()
    serializer_class = serializers.ConfigComplianceSerializer
    filterset_class = filters.ConfigComplianceFilterSet

ConfigPlanViewSet

Bases: NautobotAPIVersionMixin, NotesViewSetMixin, ModelViewSetMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin, BulkUpdateModelMixin, BulkDestroyModelMixin, GenericViewSet

API viewset for interacting with ConfigPlan objects. Does not support POST to create objects.

Source code in nautobot_golden_config/api/views.py
class ConfigPlanViewSet(
    NautobotAPIVersionMixin,
    NotesViewSetMixin,
    ModelViewSetMixin,
    RetrieveModelMixin,
    UpdateModelMixin,
    DestroyModelMixin,
    ListModelMixin,
    BulkUpdateModelMixin,
    BulkDestroyModelMixin,
    GenericViewSet,
):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with ConfigPlan objects. Does not support POST to create objects."""

    queryset = models.ConfigPlan.objects.all()
    serializer_class = serializers.ConfigPlanSerializer
    filterset_class = filters.ConfigPlanFilterSet

    def get_serializer_context(self):
        """Gather all custom fields for the model. Copied from nautobot.extras.api.views.CustomFieldModelViewSet."""
        content_type = ContentType.objects.get_for_model(self.queryset.model)
        custom_fields = content_type.custom_fields.all()

        context = super().get_serializer_context()
        context.update(
            {
                "custom_fields": custom_fields,
            }
        )
        return context
get_serializer_context()

Gather all custom fields for the model. Copied from nautobot.extras.api.views.CustomFieldModelViewSet.

Source code in nautobot_golden_config/api/views.py
def get_serializer_context(self):
    """Gather all custom fields for the model. Copied from nautobot.extras.api.views.CustomFieldModelViewSet."""
    content_type = ContentType.objects.get_for_model(self.queryset.model)
    custom_fields = content_type.custom_fields.all()

    context = super().get_serializer_context()
    context.update(
        {
            "custom_fields": custom_fields,
        }
    )
    return context

ConfigPushPermissions

Bases: BasePermission

Permissions class to validate access to Devices and GoldenConfig view.

Source code in nautobot_golden_config/api/views.py
class ConfigPushPermissions(BasePermission):
    """Permissions class to validate access to Devices and GoldenConfig view."""

    def has_permission(self, request, view):
        """Method to validated permissions to API view."""
        return request.user.has_perm("nautobot_golden_config.view_goldenconfig")

    def has_object_permission(self, request, view, obj):
        """Validate user access to the object, taking into account constraints."""
        return request.user.has_perm("dcim.view_device", obj=obj)
has_object_permission(request, view, obj)

Validate user access to the object, taking into account constraints.

Source code in nautobot_golden_config/api/views.py
def has_object_permission(self, request, view, obj):
    """Validate user access to the object, taking into account constraints."""
    return request.user.has_perm("dcim.view_device", obj=obj)
has_permission(request, view)

Method to validated permissions to API view.

Source code in nautobot_golden_config/api/views.py
def has_permission(self, request, view):
    """Method to validated permissions to API view."""
    return request.user.has_perm("nautobot_golden_config.view_goldenconfig")

ConfigRemoveViewSet

Bases: NautobotModelViewSet

API viewset for interacting with ConfigRemove objects.

Source code in nautobot_golden_config/api/views.py
class ConfigRemoveViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with ConfigRemove objects."""

    queryset = models.ConfigRemove.objects.all()
    serializer_class = serializers.ConfigRemoveSerializer
    filterset_class = filters.ConfigRemoveFilterSet

ConfigReplaceViewSet

Bases: NautobotModelViewSet

API viewset for interacting with ConfigReplace objects.

Source code in nautobot_golden_config/api/views.py
class ConfigReplaceViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with ConfigReplace objects."""

    queryset = models.ConfigReplace.objects.all()
    serializer_class = serializers.ConfigReplaceSerializer
    filterset_class = filters.ConfigReplaceFilterSet

ConfigToPushViewSet

Bases: RetrieveModelMixin, GenericViewSet

Detail REST API view showing configuration after postprocessing.

Source code in nautobot_golden_config/api/views.py
class ConfigToPushViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    """Detail REST API view showing configuration after postprocessing."""

    permission_classes = [IsAuthenticated & ConfigPushPermissions]
    queryset = Device.objects.all()
    serializer_class = serializers.ConfigToPushSerializer

GenerateIntendedConfigException

Bases: APIException

Exception for when the intended config cannot be generated.

Source code in nautobot_golden_config/api/views.py
class GenerateIntendedConfigException(APIException):
    """Exception for when the intended config cannot be generated."""

    status_code = 400
    default_detail = "Unable to generate the intended config for this device."
    default_code = "error"

GenerateIntendedConfigView

Bases: NautobotAPIVersionMixin, GenericAPIView

API view for generating the intended config for a Device.

Source code in nautobot_golden_config/api/views.py
class GenerateIntendedConfigView(NautobotAPIVersionMixin, GenericAPIView):
    """API view for generating the intended config for a Device."""

    name = "Generate Intended Config for Device"
    permission_classes = [IsAuthenticated]
    serializer_class = serializers.GenerateIntendedConfigSerializer

    def _get_diff(self, device, intended_config):
        """Generate a unified diff between the provided config and the intended config stored on the Device's GoldenConfig.intended_config."""
        diff = None
        try:
            golden_config = device.goldenconfig
            if golden_config.intended_last_success_date is not None:
                prior_intended_config = golden_config.intended_config
                diff = "".join(
                    difflib.unified_diff(
                        prior_intended_config.splitlines(keepends=True),
                        intended_config.splitlines(keepends=True),
                        fromfile="prior intended config",
                        tofile="rendered config",
                    )
                )
        except models.GoldenConfig.DoesNotExist:
            pass

        return diff

    def _get_object(self, request, model, query_param):
        """Get the requested model instance, restricted to requesting user."""
        pk = request.query_params.get(query_param)
        if not pk:
            raise GenerateIntendedConfigException(f"Parameter {query_param} is required")
        try:
            return model.objects.restrict(request.user, "view").get(pk=pk)
        except model.DoesNotExist as exc:
            raise GenerateIntendedConfigException(f"{model.__name__} with id '{pk}' not found") from exc

    def _get_jinja_template_path(self, settings, device, git_repository, base_path=None):
        """Get the Jinja template path for the device in the provided git repository."""
        try:
            rendered_path = render_jinja2(template_code=settings.jinja_path_template, context={"obj": device})
        except (TemplateSyntaxError, TemplateError) as exc:
            raise GenerateIntendedConfigException("Error rendering Jinja path template") from exc
        if base_path is None:
            filesystem_path = Path(git_repository.filesystem_path) / rendered_path
        else:
            filesystem_path = Path(base_path) / rendered_path
        if not filesystem_path.is_file():
            msg = f"Jinja template {rendered_path} not found in git repository {git_repository}"
            raise GenerateIntendedConfigException(msg)
        return filesystem_path

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="branch",
                required=False,
                type=OpenApiTypes.STR,
                location=OpenApiParameter.QUERY,
            ),
            OpenApiParameter(
                name="device_id",
                required=True,
                type=OpenApiTypes.UUID,
                location=OpenApiParameter.QUERY,
            ),
            OpenApiParameter(
                name="graphql_query_id",
                required=False,
                type=OpenApiTypes.UUID,
                location=OpenApiParameter.QUERY,
            ),
        ]
    )
    def get(self, request, *args, **kwargs):  # pylint: disable=too-many-locals, too-many-branches
        """Generate intended configuration for a Device."""
        device = self._get_object(request, Device, "device_id")
        branch_param = request.query_params.get("branch")
        if branch_param and version.parse(nautobot_settings.VERSION) < version.parse("2.4.2"):
            raise GenerateIntendedConfigException("Branch support requires Nautobot v2.4.2 or later")
        graphql_query = None
        graphql_query_id_param = request.query_params.get("graphql_query_id")
        if graphql_query_id_param:
            try:
                graphql_query = GraphQLQuery.objects.get(pk=graphql_query_id_param)
            except GraphQLQuery.DoesNotExist as exc:
                raise GenerateIntendedConfigException(
                    f"GraphQLQuery with id '{graphql_query_id_param}' not found"
                ) from exc
        settings = models.GoldenConfigSetting.objects.get_for_device(device)
        if not settings:
            raise GenerateIntendedConfigException("No Golden Config settings found for this device")
        if not settings.jinja_repository:
            raise GenerateIntendedConfigException("Golden Config settings jinja_repository not set")

        if graphql_query is None:
            if settings.sot_agg_query is not None:
                graphql_query = settings.sot_agg_query
            else:
                raise GenerateIntendedConfigException("Golden Config settings sot_agg_query not set")

        if "device_id" not in graphql_query.variables:
            raise GenerateIntendedConfigException("The selected GraphQL query is missing a 'device_id' variable")

        try:
            git_repository = settings.jinja_repository
            ensure_git_repository(git_repository)
        except Exception as exc:
            raise GenerateIntendedConfigException("Error trying to sync git repository") from exc

        status_code, graphql_data = graph_ql_query(request, device, graphql_query.query)
        if status_code == status.HTTP_200_OK:
            try:
                if branch_param:
                    with git_repository.clone_to_directory_context(branch=branch_param, depth=1) as git_repo_path:
                        filesystem_path = self._get_jinja_template_path(
                            settings, device, git_repository, base_path=git_repo_path
                        )
                        intended_config = self._render_config_nornir_serial(
                            device=device,
                            jinja_template=str(filesystem_path.relative_to(git_repo_path)),
                            jinja_root_path=git_repo_path,
                            graphql_data=graphql_data,
                        )
                else:
                    filesystem_path = self._get_jinja_template_path(settings, device, git_repository)
                    intended_config = self._render_config_nornir_serial(
                        device=device,
                        jinja_template=str(filesystem_path.relative_to(git_repository.filesystem_path)),
                        jinja_root_path=git_repository.filesystem_path,
                        graphql_data=graphql_data,
                    )
            except Exception as exc:
                raise GenerateIntendedConfigException(f"Error rendering Jinja template: {exc}") from exc

            diff = self._get_diff(device, intended_config)

            return Response(
                data={
                    "intended_config": intended_config,
                    "intended_config_lines": intended_config.split("\n"),
                    "graphql_data": graphql_data,
                    "diff": diff,
                    "diff_lines": diff.split("\n") if diff else [],
                },
                status=status.HTTP_200_OK,
            )

        raise GenerateIntendedConfigException("Unable to generate the intended config for this device")

    def _render_config_nornir_serial(self, device, jinja_template, jinja_root_path, graphql_data):
        """Render the Jinja template for the device using Nornir serial runner.

        This is a small stub of the logic in nornir_plays.config_intended.config_intended.
        """
        jinja_env = get_django_env()
        with InitNornir(
            runner={"plugin": "serial"},
            logging={"enabled": False},
            inventory={
                "plugin": "nautobot-inventory",
                "options": {
                    "credentials_class": NORNIR_SETTINGS.get("credentials"),
                    "params": NORNIR_SETTINGS.get("inventory_params"),
                    "queryset": Device.objects.filter(pk=device.pk),
                    "defaults": {"now": make_aware(datetime.datetime.now())},
                },
            },
        ) as nornir_obj:
            results = nornir_obj.run(
                task=_nornir_task_inject_graphql_data,
                name="REST API GENERATE CONFIG",
                graphql_data=graphql_data,
                obj=device,  # Used by the nornir tasks for logging to the logger below
                logger=logging.getLogger(
                    dispatcher.__module__
                ),  # The nornir tasks are built for logging to a JobResult, pass a standard logger here
                jinja_template=jinja_template,
                jinja_root_path=jinja_root_path,
                output_file_location="/dev/null",  # The nornir task outputs the templated config to a file, but this API doesn't need it
                jinja_filters=jinja_env.filters,
                jinja_env=jinja_env,
                **dispatch_params(
                    "generate_config", device.platform.network_driver, logging.getLogger(dispatch_params.__module__)
                ),
            )
            if results[device.name].failed:
                if results[device.name].exception:  # pylint: disable=no-else-raise
                    raise results[device.name].exception
                else:
                    raise GenerateIntendedConfigException(
                        f"Error generating intended config for {device.name}: {results[device.name].result}"
                    )
            else:
                return results[device.name][1][1][0].result["config"]
get(request, *args, **kwargs)

Generate intended configuration for a Device.

Source code in nautobot_golden_config/api/views.py
@extend_schema(
    parameters=[
        OpenApiParameter(
            name="branch",
            required=False,
            type=OpenApiTypes.STR,
            location=OpenApiParameter.QUERY,
        ),
        OpenApiParameter(
            name="device_id",
            required=True,
            type=OpenApiTypes.UUID,
            location=OpenApiParameter.QUERY,
        ),
        OpenApiParameter(
            name="graphql_query_id",
            required=False,
            type=OpenApiTypes.UUID,
            location=OpenApiParameter.QUERY,
        ),
    ]
)
def get(self, request, *args, **kwargs):  # pylint: disable=too-many-locals, too-many-branches
    """Generate intended configuration for a Device."""
    device = self._get_object(request, Device, "device_id")
    branch_param = request.query_params.get("branch")
    if branch_param and version.parse(nautobot_settings.VERSION) < version.parse("2.4.2"):
        raise GenerateIntendedConfigException("Branch support requires Nautobot v2.4.2 or later")
    graphql_query = None
    graphql_query_id_param = request.query_params.get("graphql_query_id")
    if graphql_query_id_param:
        try:
            graphql_query = GraphQLQuery.objects.get(pk=graphql_query_id_param)
        except GraphQLQuery.DoesNotExist as exc:
            raise GenerateIntendedConfigException(
                f"GraphQLQuery with id '{graphql_query_id_param}' not found"
            ) from exc
    settings = models.GoldenConfigSetting.objects.get_for_device(device)
    if not settings:
        raise GenerateIntendedConfigException("No Golden Config settings found for this device")
    if not settings.jinja_repository:
        raise GenerateIntendedConfigException("Golden Config settings jinja_repository not set")

    if graphql_query is None:
        if settings.sot_agg_query is not None:
            graphql_query = settings.sot_agg_query
        else:
            raise GenerateIntendedConfigException("Golden Config settings sot_agg_query not set")

    if "device_id" not in graphql_query.variables:
        raise GenerateIntendedConfigException("The selected GraphQL query is missing a 'device_id' variable")

    try:
        git_repository = settings.jinja_repository
        ensure_git_repository(git_repository)
    except Exception as exc:
        raise GenerateIntendedConfigException("Error trying to sync git repository") from exc

    status_code, graphql_data = graph_ql_query(request, device, graphql_query.query)
    if status_code == status.HTTP_200_OK:
        try:
            if branch_param:
                with git_repository.clone_to_directory_context(branch=branch_param, depth=1) as git_repo_path:
                    filesystem_path = self._get_jinja_template_path(
                        settings, device, git_repository, base_path=git_repo_path
                    )
                    intended_config = self._render_config_nornir_serial(
                        device=device,
                        jinja_template=str(filesystem_path.relative_to(git_repo_path)),
                        jinja_root_path=git_repo_path,
                        graphql_data=graphql_data,
                    )
            else:
                filesystem_path = self._get_jinja_template_path(settings, device, git_repository)
                intended_config = self._render_config_nornir_serial(
                    device=device,
                    jinja_template=str(filesystem_path.relative_to(git_repository.filesystem_path)),
                    jinja_root_path=git_repository.filesystem_path,
                    graphql_data=graphql_data,
                )
        except Exception as exc:
            raise GenerateIntendedConfigException(f"Error rendering Jinja template: {exc}") from exc

        diff = self._get_diff(device, intended_config)

        return Response(
            data={
                "intended_config": intended_config,
                "intended_config_lines": intended_config.split("\n"),
                "graphql_data": graphql_data,
                "diff": diff,
                "diff_lines": diff.split("\n") if diff else [],
            },
            status=status.HTTP_200_OK,
        )

    raise GenerateIntendedConfigException("Unable to generate the intended config for this device")

GitRepositoryBranchesView

Bases: NautobotAPIVersionMixin, RetrieveAPIView

API view for extras.GitRepository with branches.

Source code in nautobot_golden_config/api/views.py
@extend_schema(exclude=True)
class GitRepositoryBranchesView(NautobotAPIVersionMixin, RetrieveAPIView):
    """API view for extras.GitRepository with branches."""

    name = "Git Repository with Branches"
    permission_classes = [IsAuthenticated]
    queryset = GitRepository.objects.all()
    serializer_class = serializers.GitRepositoryWithBranchesSerializer

    def get_queryset(self):
        """Override the original get_queryset to apply permissions."""
        queryset = super().get_queryset()
        return queryset.restrict(self.request.user, "view")
get_queryset()

Override the original get_queryset to apply permissions.

Source code in nautobot_golden_config/api/views.py
def get_queryset(self):
    """Override the original get_queryset to apply permissions."""
    queryset = super().get_queryset()
    return queryset.restrict(self.request.user, "view")

GoldenConfigRootView

Bases: APIRootView

Golden Config API root view.

Source code in nautobot_golden_config/api/views.py
class GoldenConfigRootView(APIRootView):
    """Golden Config API root view."""

    def get_view_name(self):
        """Golden Config API root view boilerplate."""
        return "Golden Config"
get_view_name()

Golden Config API root view boilerplate.

Source code in nautobot_golden_config/api/views.py
def get_view_name(self):
    """Golden Config API root view boilerplate."""
    return "Golden Config"

GoldenConfigSettingViewSet

Bases: NautobotModelViewSet

API viewset for interacting with GoldenConfigSetting objects.

Source code in nautobot_golden_config/api/views.py
class GoldenConfigSettingViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with GoldenConfigSetting objects."""

    queryset = models.GoldenConfigSetting.objects.all()
    serializer_class = serializers.GoldenConfigSettingSerializer
    filterset_class = filters.GoldenConfigSettingFilterSet

GoldenConfigViewSet

Bases: NautobotModelViewSet

API viewset for interacting with GoldenConfig objects.

Source code in nautobot_golden_config/api/views.py
class GoldenConfigViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with GoldenConfig objects."""

    queryset = models.GoldenConfig.objects.all()
    serializer_class = serializers.GoldenConfigSerializer
    filterset_class = filters.GoldenConfigFilterSet

RemediationSettingViewSet

Bases: NautobotModelViewSet

API viewset for interacting with RemediationSetting objects.

Source code in nautobot_golden_config/api/views.py
class RemediationSettingViewSet(NautobotModelViewSet):  # pylint:disable=too-many-ancestors
    """API viewset for interacting with RemediationSetting objects."""

    queryset = models.RemediationSetting.objects.all()
    serializer_class = serializers.RemediationSettingSerializer
    filterset_class = filters.RemediationSettingFilterSet

SOTAggDeviceDetailView

Bases: NautobotAPIVersionMixin, GenericAPIView

Detail REST API view showing graphql, with a potential "transformer" of data on a specific device.

Source code in nautobot_golden_config/api/views.py
class SOTAggDeviceDetailView(NautobotAPIVersionMixin, GenericAPIView):
    """Detail REST API view showing graphql, with a potential "transformer" of data on a specific device."""

    name = "SOTAgg Device View"
    permission_classes = [IsAuthenticated]
    serializer_class = serializers.GraphQLSerializer

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="graphql_query_id",
                required=False,
                type=OpenApiTypes.UUID,
                location=OpenApiParameter.QUERY,
            ),
        ]
    )
    def get(self, request, *args, **kwargs):
        """Get GraphQL data for a Device."""
        device = get_object_or_404(Device.objects.restrict(request.user, "view"), pk=kwargs["pk"])

        graphql_query = None
        graphql_query_id_param = request.query_params.get("graphql_query_id")
        if graphql_query_id_param:
            try:
                graphql_query = GraphQLQuery.objects.get(pk=graphql_query_id_param)
            except GraphQLQuery.DoesNotExist as exc:
                raise ValidationError(f"GraphQLQuery with id '{graphql_query_id_param}' not found") from exc
        settings = models.GoldenConfigSetting.objects.get_for_device(device)

        if graphql_query is None:
            if settings.sot_agg_query is not None:
                graphql_query = settings.sot_agg_query
            else:
                raise ValidationError("Golden Config settings sot_agg_query not set")

        if "device_id" not in graphql_query.variables:
            raise ValidationError("The selected GraphQL query is missing a 'device_id' variable")

        status_code, graphql_data = graph_ql_query(request, device, graphql_query.query)
        if status_code == status.HTTP_200_OK:
            return Response(
                data=graphql_data,
                status=status.HTTP_200_OK,
            )

        raise ValidationError("Unable to generate the GraphQL data for this device")
get(request, *args, **kwargs)

Get GraphQL data for a Device.

Source code in nautobot_golden_config/api/views.py
@extend_schema(
    parameters=[
        OpenApiParameter(
            name="graphql_query_id",
            required=False,
            type=OpenApiTypes.UUID,
            location=OpenApiParameter.QUERY,
        ),
    ]
)
def get(self, request, *args, **kwargs):
    """Get GraphQL data for a Device."""
    device = get_object_or_404(Device.objects.restrict(request.user, "view"), pk=kwargs["pk"])

    graphql_query = None
    graphql_query_id_param = request.query_params.get("graphql_query_id")
    if graphql_query_id_param:
        try:
            graphql_query = GraphQLQuery.objects.get(pk=graphql_query_id_param)
        except GraphQLQuery.DoesNotExist as exc:
            raise ValidationError(f"GraphQLQuery with id '{graphql_query_id_param}' not found") from exc
    settings = models.GoldenConfigSetting.objects.get_for_device(device)

    if graphql_query is None:
        if settings.sot_agg_query is not None:
            graphql_query = settings.sot_agg_query
        else:
            raise ValidationError("Golden Config settings sot_agg_query not set")

    if "device_id" not in graphql_query.variables:
        raise ValidationError("The selected GraphQL query is missing a 'device_id' variable")

    status_code, graphql_data = graph_ql_query(request, device, graphql_query.query)
    if status_code == status.HTTP_200_OK:
        return Response(
            data=graphql_data,
            status=status.HTTP_200_OK,
        )

    raise ValidationError("Unable to generate the GraphQL data for this device")