# What this PR does - The `service_name` label will be added to Grafana Alerting integration when it is created, if it wasn't added by user. - Adds celery task that should be started manually and will add the `service_name` dynamic label to all existing Grafana Alerting integrations. ## Which issue(s) this PR closes Related to https://github.com/grafana/oncall-private/issues/2975 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --------- Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com>
210 lines
8.9 KiB
Python
210 lines
8.9 KiB
Python
import logging
|
||
|
||
import requests
|
||
from drf_spectacular.utils import extend_schema, inline_serializer
|
||
from rest_framework.exceptions import NotFound
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from rest_framework.viewsets import ViewSet
|
||
|
||
from apps.api.permissions import RBACPermission
|
||
from apps.api.serializers.labels import (
|
||
LabelKeySerializer,
|
||
LabelOptionSerializer,
|
||
LabelReprSerializer,
|
||
LabelValueSerializer,
|
||
)
|
||
from apps.auth_token.auth import PluginAuthentication
|
||
from apps.labels.client import LabelsAPIClient, LabelsRepoAPIException
|
||
from apps.labels.tasks import update_instances_labels_cache, update_label_option_cache
|
||
from apps.labels.types import LabelOption
|
||
from apps.labels.utils import is_labels_feature_enabled
|
||
from common.api_helpers.exceptions import BadRequest
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class LabelsFeatureFlagViewSet(ViewSet):
|
||
def initial(self, request, *args, **kwargs):
|
||
if not is_labels_feature_enabled(self.request.auth.organization):
|
||
raise NotFound
|
||
super().initial(request, *args, **kwargs)
|
||
|
||
|
||
class LabelsViewSet(LabelsFeatureFlagViewSet):
|
||
"""
|
||
Proxy requests to labels-app to create/update labels
|
||
"""
|
||
|
||
permission_classes = (IsAuthenticated, RBACPermission)
|
||
authentication_classes = (PluginAuthentication,)
|
||
rbac_permissions = {
|
||
"create_label": [RBACPermission.Permissions.LABEL_CREATE],
|
||
"rename_key": [RBACPermission.Permissions.LABEL_WRITE],
|
||
"add_value": [RBACPermission.Permissions.LABEL_WRITE],
|
||
"rename_value": [RBACPermission.Permissions.LABEL_WRITE],
|
||
"get_keys": [RBACPermission.Permissions.LABEL_READ],
|
||
"get_key": [RBACPermission.Permissions.LABEL_READ],
|
||
"get_key_by_name": [RBACPermission.Permissions.LABEL_READ],
|
||
"get_value": [RBACPermission.Permissions.LABEL_READ],
|
||
}
|
||
|
||
@extend_schema(responses=LabelKeySerializer(many=True))
|
||
def get_keys(self, request):
|
||
"""List of labels keys"""
|
||
organization = self.request.auth.organization
|
||
keys, response = LabelsAPIClient(organization.grafana_url, organization.api_token).get_keys()
|
||
return Response(keys, status=response.status_code)
|
||
|
||
@extend_schema(responses=LabelOptionSerializer)
|
||
def get_key(self, request, key_id):
|
||
"""
|
||
get_key returns LabelOption – key with the list of values
|
||
"""
|
||
organization = self.request.auth.organization
|
||
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).get_label_by_key_id(
|
||
key_id
|
||
)
|
||
self._update_labels_cache(label_option)
|
||
return Response(label_option, status=response.status_code)
|
||
|
||
@extend_schema(responses=LabelOptionSerializer)
|
||
def get_key_by_name(self, request, key_name):
|
||
"""
|
||
get_key_by_name returns LabelOption – key with the list of values
|
||
"""
|
||
organization = self.request.auth.organization
|
||
label_option, response = LabelsAPIClient(
|
||
organization.grafana_url,
|
||
organization.api_token,
|
||
).get_label_by_key_name(key_name)
|
||
return Response(label_option, status=response.status_code)
|
||
|
||
@extend_schema(responses=LabelValueSerializer)
|
||
def get_value(self, request, key_id, value_id):
|
||
"""get_value returns a Value"""
|
||
organization = self.request.auth.organization
|
||
value, response = LabelsAPIClient(organization.grafana_url, organization.api_token).get_value(key_id, value_id)
|
||
# TODO: update_labels_cache expects LabelOption, but get value returns a Value. Investigate, temporary disable.
|
||
# self._update_labels_cache(value)
|
||
return Response(value, status=response.status_code)
|
||
|
||
@extend_schema(request=LabelReprSerializer, responses=LabelOptionSerializer)
|
||
def rename_key(self, request, key_id):
|
||
"""Rename the key"""
|
||
organization = self.request.auth.organization
|
||
label_data = self.request.data
|
||
if not label_data:
|
||
raise BadRequest(detail="name is required")
|
||
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).rename_key(
|
||
key_id, label_data
|
||
)
|
||
self._update_labels_cache(label_option)
|
||
return Response(label_option, status=response.status_code)
|
||
|
||
@extend_schema(
|
||
request=inline_serializer(
|
||
name="LabelCreateSerializer",
|
||
fields={"key": LabelReprSerializer(), "values": LabelReprSerializer(many=True)},
|
||
many=True,
|
||
),
|
||
responses={201: LabelOptionSerializer},
|
||
)
|
||
def create_label(self, request):
|
||
"""Create a new label key with values(Optional)"""
|
||
organization = self.request.auth.organization
|
||
label_data = self.request.data
|
||
if not label_data:
|
||
raise BadRequest(detail="key data (name, values) is required")
|
||
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).create_label(
|
||
label_data
|
||
)
|
||
return Response(label_option, status=response.status_code)
|
||
|
||
@extend_schema(request=LabelReprSerializer, responses=LabelOptionSerializer)
|
||
def add_value(self, request, key_id):
|
||
"""Add a new value to the key"""
|
||
organization = self.request.auth.organization
|
||
label_data = self.request.data
|
||
if not label_data:
|
||
raise BadRequest(detail="name is required")
|
||
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).add_value(
|
||
key_id, label_data
|
||
)
|
||
return Response(label_option, status=response.status_code)
|
||
|
||
@extend_schema(request=LabelReprSerializer, responses=LabelOptionSerializer)
|
||
def rename_value(self, request, key_id, value_id):
|
||
"""Rename the value"""
|
||
organization = self.request.auth.organization
|
||
label_data = self.request.data
|
||
if not label_data:
|
||
raise BadRequest(detail="name is required")
|
||
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).rename_value(
|
||
key_id, value_id, label_data
|
||
)
|
||
status = response.status_code
|
||
self._update_labels_cache(label_option)
|
||
return Response(label_option, status=status)
|
||
|
||
def _update_labels_cache(self, label_option: LabelOption):
|
||
if not label_option:
|
||
return
|
||
serializer = LabelOptionSerializer(data=label_option)
|
||
if serializer.is_valid():
|
||
update_label_option_cache.apply_async((label_option,))
|
||
# update_labels_cache.apply_async((label_data,))
|
||
|
||
def handle_exception(self, exc):
|
||
if isinstance(exc, LabelsRepoAPIException):
|
||
logging.error(f'msg="LabelsViewSet: LabelRepo error: {exc}"')
|
||
return Response({"message": exc.msg}, status=exc.status)
|
||
elif isinstance(exc, requests.RequestException):
|
||
logging.error(f'msg="LabelsViewSet: error while requesting LabelRepo: {exc}"')
|
||
return Response({"message": "Something went wrong"}, status=500)
|
||
else:
|
||
return super().handle_exception(exc)
|
||
|
||
|
||
# specifying a tag explicitly to avoid these endpoints being grouped with alert group endpoints
|
||
@extend_schema(tags=["alert group labels"])
|
||
class AlertGroupLabelsViewSet(LabelsFeatureFlagViewSet):
|
||
"""
|
||
This viewset is similar to LabelsViewSet, but it works with alert group labels.
|
||
Alert group labels are stored in the database, not in the label repo.
|
||
"""
|
||
|
||
permission_classes = (IsAuthenticated, RBACPermission)
|
||
authentication_classes = (PluginAuthentication,)
|
||
rbac_permissions = {
|
||
"get_keys": [RBACPermission.Permissions.ALERT_GROUPS_READ],
|
||
"get_key": [RBACPermission.Permissions.ALERT_GROUPS_READ],
|
||
}
|
||
|
||
@extend_schema(responses=LabelKeySerializer(many=True))
|
||
def get_keys(self, request):
|
||
"""
|
||
List of alert group label keys.
|
||
IDs are the same as names to keep the response format consistent with LabelsViewSet.get_keys().
|
||
"""
|
||
names = self.request.auth.organization.alert_group_labels.values_list("key_name", flat=True).distinct()
|
||
return Response([{"id": name, "name": name} for name in names])
|
||
|
||
@extend_schema(responses=LabelOptionSerializer)
|
||
def get_key(self, request, key_id):
|
||
"""Key with the list of values. IDs and names are interchangeable (see get_keys() for more details)."""
|
||
values = (
|
||
self.request.auth.organization.alert_group_labels.filter(key_name=key_id)
|
||
.values_list("value_name", flat=True)
|
||
.distinct()
|
||
)
|
||
return Response(
|
||
{"key": {"id": key_id, "name": key_id}, "values": [{"id": value, "name": value} for value in values]}
|
||
)
|
||
|
||
|
||
def schedule_update_label_cache(model_name, org, ids):
|
||
if not is_labels_feature_enabled(org):
|
||
return
|
||
logger.info(f"start update_instances_labels_cache for ids: {ids}")
|
||
update_instances_labels_cache.apply_async((org.id, ids, model_name))
|