2023-10-20 09:30:11 +02:00
|
|
|
|
import logging
|
|
|
|
|
|
|
2023-11-29 16:56:42 +08:00
|
|
|
|
import requests
|
2023-10-20 09:30:11 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
2024-10-02 13:39:49 -04:00
|
|
|
|
from apps.api.permissions import RBACPermission
|
2023-10-20 09:30:11 +02:00
|
|
|
|
from apps.api.serializers.labels import (
|
|
|
|
|
|
LabelKeySerializer,
|
2024-02-20 14:42:51 +08:00
|
|
|
|
LabelOptionSerializer,
|
2023-10-20 09:30:11 +02:00
|
|
|
|
LabelReprSerializer,
|
|
|
|
|
|
LabelValueSerializer,
|
|
|
|
|
|
)
|
|
|
|
|
|
from apps.auth_token.auth import PluginAuthentication
|
2023-11-29 16:56:42 +08:00
|
|
|
|
from apps.labels.client import LabelsAPIClient, LabelsRepoAPIException
|
2024-02-20 14:42:51 +08:00
|
|
|
|
from apps.labels.tasks import update_instances_labels_cache, update_label_option_cache
|
2025-01-14 11:02:23 +01:00
|
|
|
|
from apps.labels.types import LabelOption
|
2023-10-20 09:30:11 +02:00
|
|
|
|
from apps.labels.utils import is_labels_feature_enabled
|
|
|
|
|
|
from common.api_helpers.exceptions import BadRequest
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-06 10:31:12 +00:00
|
|
|
|
class LabelsFeatureFlagViewSet(ViewSet):
|
|
|
|
|
|
def initial(self, request, *args, **kwargs):
|
|
|
|
|
|
if not is_labels_feature_enabled(self.request.auth.organization):
|
|
|
|
|
|
raise NotFound
|
2023-12-06 10:57:07 +00:00
|
|
|
|
super().initial(request, *args, **kwargs)
|
2023-11-06 10:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LabelsViewSet(LabelsFeatureFlagViewSet):
|
2023-10-20 09:30:11 +02:00
|
|
|
|
"""
|
|
|
|
|
|
Proxy requests to labels-app to create/update labels
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2024-10-02 13:39:49 -04:00
|
|
|
|
permission_classes = (IsAuthenticated, RBACPermission)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
authentication_classes = (PluginAuthentication,)
|
2024-10-02 13:39:49 -04:00
|
|
|
|
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],
|
2025-01-14 11:02:23 +01:00
|
|
|
|
"get_key_by_name": [RBACPermission.Permissions.LABEL_READ],
|
2024-10-02 13:39:49 -04:00
|
|
|
|
"get_value": [RBACPermission.Permissions.LABEL_READ],
|
2023-10-23 13:03:51 +02:00
|
|
|
|
}
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
|
|
|
|
|
@extend_schema(responses=LabelKeySerializer(many=True))
|
|
|
|
|
|
def get_keys(self, request):
|
|
|
|
|
|
"""List of labels keys"""
|
|
|
|
|
|
organization = self.request.auth.organization
|
2024-02-20 14:42:51 +08:00
|
|
|
|
keys, response = LabelsAPIClient(organization.grafana_url, organization.api_token).get_keys()
|
|
|
|
|
|
return Response(keys, status=response.status_code)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2024-02-20 14:42:51 +08:00
|
|
|
|
@extend_schema(responses=LabelOptionSerializer)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
def get_key(self, request, key_id):
|
2024-02-20 14:42:51 +08:00
|
|
|
|
"""
|
|
|
|
|
|
get_key returns LabelOption – key with the list of values
|
|
|
|
|
|
"""
|
2023-10-20 09:30:11 +02:00
|
|
|
|
organization = self.request.auth.organization
|
2024-02-20 14:42:51 +08:00
|
|
|
|
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)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2025-01-14 11:02:23 +01:00
|
|
|
|
@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)
|
|
|
|
|
|
|
2023-10-20 09:30:11 +02:00
|
|
|
|
@extend_schema(responses=LabelValueSerializer)
|
|
|
|
|
|
def get_value(self, request, key_id, value_id):
|
2024-02-20 14:42:51 +08:00
|
|
|
|
"""get_value returns a Value"""
|
2023-10-20 09:30:11 +02:00
|
|
|
|
organization = self.request.auth.organization
|
2024-02-20 14:42:51 +08:00
|
|
|
|
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)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2024-02-20 14:42:51 +08:00
|
|
|
|
@extend_schema(request=LabelReprSerializer, responses=LabelOptionSerializer)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
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")
|
2024-02-20 14:42:51 +08:00
|
|
|
|
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).rename_key(
|
2023-10-20 09:30:11 +02:00
|
|
|
|
key_id, label_data
|
|
|
|
|
|
)
|
2024-02-20 14:42:51 +08:00
|
|
|
|
self._update_labels_cache(label_option)
|
|
|
|
|
|
return Response(label_option, status=response.status_code)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
|
|
|
|
|
@extend_schema(
|
|
|
|
|
|
request=inline_serializer(
|
|
|
|
|
|
name="LabelCreateSerializer",
|
|
|
|
|
|
fields={"key": LabelReprSerializer(), "values": LabelReprSerializer(many=True)},
|
|
|
|
|
|
many=True,
|
|
|
|
|
|
),
|
2024-02-20 14:42:51 +08:00
|
|
|
|
responses={201: LabelOptionSerializer},
|
2023-10-20 09:30:11 +02:00
|
|
|
|
)
|
|
|
|
|
|
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")
|
2024-02-20 14:42:51 +08:00
|
|
|
|
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).create_label(
|
|
|
|
|
|
label_data
|
|
|
|
|
|
)
|
|
|
|
|
|
return Response(label_option, status=response.status_code)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2024-02-20 14:42:51 +08:00
|
|
|
|
@extend_schema(request=LabelReprSerializer, responses=LabelOptionSerializer)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
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")
|
2024-02-20 14:42:51 +08:00
|
|
|
|
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).add_value(
|
2023-10-20 09:30:11 +02:00
|
|
|
|
key_id, label_data
|
|
|
|
|
|
)
|
2024-02-20 14:42:51 +08:00
|
|
|
|
return Response(label_option, status=response.status_code)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2024-02-20 14:42:51 +08:00
|
|
|
|
@extend_schema(request=LabelReprSerializer, responses=LabelOptionSerializer)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
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")
|
2024-02-20 14:42:51 +08:00
|
|
|
|
label_option, response = LabelsAPIClient(organization.grafana_url, organization.api_token).rename_value(
|
2023-10-20 09:30:11 +02:00
|
|
|
|
key_id, value_id, label_data
|
|
|
|
|
|
)
|
2023-11-29 16:56:42 +08:00
|
|
|
|
status = response.status_code
|
2024-02-20 14:42:51 +08:00
|
|
|
|
self._update_labels_cache(label_option)
|
|
|
|
|
|
return Response(label_option, status=status)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2025-01-14 11:02:23 +01:00
|
|
|
|
def _update_labels_cache(self, label_option: LabelOption):
|
2024-02-20 14:42:51 +08:00
|
|
|
|
if not label_option:
|
2023-10-20 09:30:11 +02:00
|
|
|
|
return
|
2024-02-20 14:42:51 +08:00
|
|
|
|
serializer = LabelOptionSerializer(data=label_option)
|
2023-10-20 09:30:11 +02:00
|
|
|
|
if serializer.is_valid():
|
2024-02-20 14:42:51 +08:00
|
|
|
|
update_label_option_cache.apply_async((label_option,))
|
|
|
|
|
|
# update_labels_cache.apply_async((label_data,))
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2023-11-29 16:56:42 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2023-10-20 09:30:11 +02:00
|
|
|
|
|
2024-01-12 15:11:22 +00:00
|
|
|
|
# specifying a tag explicitly to avoid these endpoints being grouped with alert group endpoints
|
|
|
|
|
|
@extend_schema(tags=["alert group labels"])
|
2023-11-06 10:31:12 +00:00
|
|
|
|
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.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2024-10-02 13:39:49 -04:00
|
|
|
|
permission_classes = (IsAuthenticated, RBACPermission)
|
2023-11-06 10:31:12 +00:00
|
|
|
|
authentication_classes = (PluginAuthentication,)
|
2024-10-02 13:39:49 -04:00
|
|
|
|
rbac_permissions = {
|
|
|
|
|
|
"get_keys": [RBACPermission.Permissions.ALERT_GROUPS_READ],
|
|
|
|
|
|
"get_key": [RBACPermission.Permissions.ALERT_GROUPS_READ],
|
2023-11-06 10:31:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@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])
|
|
|
|
|
|
|
2024-02-20 14:42:51 +08:00
|
|
|
|
@extend_schema(responses=LabelOptionSerializer)
|
2023-11-06 10:31:12 +00:00
|
|
|
|
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]}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
Webhook labels (#3383)
This PR add labels for webhooks.
1. Make webhook "labelable" with ability to filter by labels.
2. Add labels to the webhook payload. It contain new field webhook with
it's name, id and labels. Field integration and alert_group has a
corresponding label field as well. See example of a new payload below:
```
{
"event": {
"type": "escalation"
},
"user": null,
"alert_group": {
"id": "IRFN6ZD31N31B",
"integration_id": "CTWM7U4A2QG97",
"route_id": "RUE7U7Z46SKGY",
"alerts_count": 1,
"state": "firing",
"created_at": "2023-11-22T08:54:55.178243Z",
"resolved_at": null,
"acknowledged_at": null,
"title": "Incident",
"permalinks": {
"slack": null,
"telegram": null,
"web": "http://grafana:3000/a/grafana-oncall-app/alert-groups/IRFN6ZD31N31B"
},
"labels": {
"severity": "critical"
}
},
"alert_group_id": "IRFN6ZD31N31B",
"alert_payload": {
"message": "This alert was sent by user for demonstration purposes"
},
"integration": {
"id": "CTWM7U4A2QG97",
"type": "webhook",
"name": "hi - Webhook",
"team": null,
"labels": {
"hello": "world",
"severity": "critical"
}
},
"notified_users": [],
"users_to_be_notified": [],
"webhook": {
"id": "WHAXK4BTC7TAEQ",
"name": "test",
"labels": {
"hello": "kesha"
}
}
}
```
I feel that there is an opportunity to make code cleaner - remove all
label logic from serializers, views and utils to models or dedicated
LabelerService and introduce Labelable interface with something like
label_verbal, update_labels methods. However, I don't want to tie
webhook labels with a refactoring.
---------
Co-authored-by: Dominik <dominik.broj@grafana.com>
2023-11-22 19:17:41 +08:00
|
|
|
|
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))
|