feat: persist is_grafana_irm_enabled from backend plugin sync data (#5171)
# What this PR does Will start persisting the `organization.is_grafana_irm_enabled` flag from the backend plugin's sync data that is sent to the oncall backend. The implications of this are that when `is_grafana_irm_enabled` is set to True, we will: - start using `grafana-irm-app` prefixed RBAC permissions (RBAC permissions for `grafana-irm-app`, as well as `grafana-oncall-app`, are already being synced to the OnCall backend since https://github.com/grafana/irm/pull/200 was merged/deployed) - start building UI URLs w/ `grafana-irm-app` instead of `grafana-oncall-app` ## Which issue(s) this PR closes Closes https://github.com/grafana/irm/issues/242 ## 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.
This commit is contained in:
parent
22ab249b3b
commit
673d2e9595
9 changed files with 117 additions and 4 deletions
|
|
@ -11,6 +11,9 @@ from rest_framework import status
|
|||
from apps.api.permissions import GrafanaAPIPermission, GrafanaAPIPermissions
|
||||
from common.constants.plugin_ids import PluginID
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from apps.user_management.models import Organization
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -309,6 +312,9 @@ class GrafanaAPIClient(APIClient):
|
|||
def get_grafana_labels_plugin_settings(self) -> APIClientResponse["GrafanaAPIClient.Types.PluginSettings"]:
|
||||
return self.get_grafana_plugin_settings(PluginID.LABELS)
|
||||
|
||||
def get_grafana_irm_plugin_settings(self) -> APIClientResponse["GrafanaAPIClient.Types.PluginSettings"]:
|
||||
return self.get_grafana_plugin_settings(PluginID.IRM)
|
||||
|
||||
def get_service_account(self, login: str) -> APIClientResponse["GrafanaAPIClient.Types.ServiceAccountResponse"]:
|
||||
return self.api_get(f"api/serviceaccounts/search?query={login}")
|
||||
|
||||
|
|
@ -328,8 +334,8 @@ class GrafanaAPIClient(APIClient):
|
|||
def get_service_account_token_permissions(self) -> APIClientResponse[typing.Dict[str, typing.List[str]]]:
|
||||
return self.api_get("api/access-control/user/permissions")
|
||||
|
||||
def sync(self) -> APIClientResponse:
|
||||
return self.api_post("api/plugins/grafana-oncall-app/resources/plugin/sync")
|
||||
def sync(self, organization: "Organization") -> APIClientResponse:
|
||||
return self.api_post(f"api/plugins/{organization.active_ui_plugin_id}/resources/plugin/sync")
|
||||
|
||||
@staticmethod
|
||||
def validate_grafana_token_format(grafana_token: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class SyncOnCallSettingsSerializer(serializers.Serializer):
|
|||
incident_enabled = serializers.BooleanField()
|
||||
incident_backend_url = serializers.CharField(allow_blank=True)
|
||||
labels_enabled = serializers.BooleanField()
|
||||
irm_enabled = serializers.BooleanField(default=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
return SyncSettings(**validated_data)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class SyncSettings:
|
|||
incident_enabled: bool
|
||||
incident_backend_url: str
|
||||
labels_enabled: bool
|
||||
irm_enabled: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ def sync_organizations_v2(org_ids=None):
|
|||
organization_qs = Organization.objects.filter(id__in=org_ids)
|
||||
for org in organization_qs:
|
||||
client = GrafanaAPIClient(api_url=org.grafana_url, api_token=org.api_token)
|
||||
_, status = client.sync()
|
||||
_, status = client.sync(org)
|
||||
if status["status_code"] != 200:
|
||||
logger.error(
|
||||
f"Failed to request sync org_id={org.pk} stack_slug={org.stack_slug} status_code={status['status_code']} url={status['url']} message={status['message']}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ from rest_framework.test import APIClient
|
|||
from apps.api.permissions import LegacyAccessControlRole
|
||||
from apps.grafana_plugin.serializers.sync_data import SyncTeamSerializer
|
||||
from apps.grafana_plugin.sync_data import SyncData, SyncSettings, SyncUser
|
||||
from apps.grafana_plugin.tasks.sync_v2 import start_sync_organizations_v2
|
||||
from apps.grafana_plugin.tasks.sync_v2 import start_sync_organizations_v2, sync_organizations_v2
|
||||
from common.constants.plugin_ids import PluginID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
@ -121,6 +122,7 @@ def test_sync_v2_content_encoding(
|
|||
incident_enabled=False,
|
||||
incident_backend_url="",
|
||||
labels_enabled=False,
|
||||
irm_enabled=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -140,6 +142,57 @@ def test_sync_v2_content_encoding(
|
|||
mock_sync.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"irm_enabled,expected",
|
||||
[
|
||||
(True, True),
|
||||
(False, False),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_sync_v2_irm_enabled(
|
||||
make_organization_and_user_with_plugin_token,
|
||||
make_user_auth_headers,
|
||||
settings,
|
||||
irm_enabled,
|
||||
expected,
|
||||
):
|
||||
settings.LICENSE = settings.CLOUD_LICENSE_NAME
|
||||
organization, _, token = make_organization_and_user_with_plugin_token()
|
||||
|
||||
assert organization.is_grafana_irm_enabled is False
|
||||
|
||||
client = APIClient()
|
||||
headers = make_user_auth_headers(None, token, organization=organization)
|
||||
url = reverse("grafana-plugin:sync-v2")
|
||||
|
||||
data = SyncData(
|
||||
users=[],
|
||||
teams=[],
|
||||
team_members={},
|
||||
settings=SyncSettings(
|
||||
stack_id=organization.stack_id,
|
||||
org_id=organization.org_id,
|
||||
license=settings.CLOUD_LICENSE_NAME,
|
||||
oncall_api_url="http://localhost",
|
||||
oncall_token="",
|
||||
grafana_url="http://localhost",
|
||||
grafana_token="fake_token",
|
||||
rbac_enabled=False,
|
||||
incident_enabled=False,
|
||||
incident_backend_url="",
|
||||
labels_enabled=False,
|
||||
irm_enabled=irm_enabled,
|
||||
),
|
||||
)
|
||||
|
||||
response = client.post(url, format="json", data=asdict(data), **headers)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
organization.refresh_from_db()
|
||||
assert organization.is_grafana_irm_enabled == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_team, validation_pass",
|
||||
[
|
||||
|
|
@ -190,3 +243,23 @@ def test_sync_batch_tasks(make_organization, settings):
|
|||
assert check_call(actual_call, expected_call)
|
||||
|
||||
assert mock_sync.call_count == len(expected_calls)
|
||||
|
||||
|
||||
@patch(
|
||||
"apps.grafana_plugin.tasks.sync_v2.GrafanaAPIClient.api_post",
|
||||
return_value=(None, {"status_code": status.HTTP_200_OK}),
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"is_grafana_irm_enabled,expected",
|
||||
[
|
||||
(True, PluginID.IRM),
|
||||
(False, PluginID.ONCALL),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_sync_organizations_v2_calls_right_backend_plugin_sync_endpoint(
|
||||
mocked_grafana_api_client_api_post, make_organization, is_grafana_irm_enabled, expected
|
||||
):
|
||||
org = make_organization(is_grafana_irm_enabled=is_grafana_irm_enabled)
|
||||
sync_organizations_v2(org_ids=[org.pk])
|
||||
mocked_grafana_api_client_api_post.assert_called_once_with(f"api/plugins/{expected}/resources/plugin/sync")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from apps.chatops_proxy.utils import (
|
|||
from apps.grafana_plugin.ui_url_builder import UIURLBuilder
|
||||
from apps.user_management.subscription_strategy import FreePublicBetaSubscriptionStrategy
|
||||
from apps.user_management.types import AlertGroupTableColumn
|
||||
from common.constants.plugin_ids import PluginID
|
||||
from common.insight_log import ChatOpsEvent, ChatOpsTypePlug, write_chatops_insight_log
|
||||
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
|
||||
|
||||
|
|
@ -352,6 +353,13 @@ class Organization(MaintainableObject):
|
|||
"""
|
||||
return UIURLBuilder(self).home(f"?oncall-uuid={self.uuid}")
|
||||
|
||||
@property
|
||||
def active_ui_plugin_id(self) -> str:
|
||||
"""
|
||||
If `is_grafana_irm_enabled` is True, this will be IRM, otherwise OnCall
|
||||
"""
|
||||
return PluginID.IRM if self.is_grafana_irm_enabled else PluginID.ONCALL
|
||||
|
||||
@classmethod
|
||||
def __str__(self):
|
||||
return f"{self.pk}: {self.org_title}"
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ def _sync_organization(organization: Organization) -> None:
|
|||
if grafana_labels_plugin_settings is not None:
|
||||
is_grafana_labels_enabled = grafana_labels_plugin_settings["enabled"]
|
||||
|
||||
# get IRM plugin settings
|
||||
is_grafana_irm_enabled = False
|
||||
grafana_irm_plugin_settings, _ = grafana_api_client.get_grafana_labels_plugin_settings()
|
||||
if grafana_irm_plugin_settings is not None:
|
||||
is_grafana_irm_enabled = grafana_irm_plugin_settings["enabled"]
|
||||
|
||||
oncall_api_url = settings.BASE_URL
|
||||
if settings.LICENSE == CLOUD_LICENSE_NAME:
|
||||
oncall_api_url = settings.GRAFANA_CLOUD_ONCALL_API_URL
|
||||
|
|
@ -85,6 +91,7 @@ def _sync_organization(organization: Organization) -> None:
|
|||
incident_enabled=is_grafana_incident_enabled,
|
||||
incident_backend_url=grafana_incident_backend_url,
|
||||
labels_enabled=is_grafana_labels_enabled,
|
||||
irm_enabled=is_grafana_irm_enabled,
|
||||
)
|
||||
_sync_organization_data(organization, sync_settings)
|
||||
if organization.api_token_status == Organization.API_TOKEN_STATUS_OK:
|
||||
|
|
@ -288,6 +295,7 @@ def _sync_organization_data(organization: Organization, sync_settings: SyncSetti
|
|||
organization.is_rbac_permissions_enabled = sync_settings.rbac_enabled
|
||||
logger.info(f"RBAC status org={organization.pk} rbac_enabled={organization.is_rbac_permissions_enabled}")
|
||||
|
||||
organization.is_grafana_irm_enabled = sync_settings.irm_enabled
|
||||
organization.is_grafana_labels_enabled = sync_settings.labels_enabled
|
||||
organization.is_grafana_incident_enabled = sync_settings.incident_enabled
|
||||
organization.grafana_incident_backend_url = sync_settings.incident_backend_url
|
||||
|
|
@ -321,6 +329,7 @@ def _sync_organization_data(organization: Organization, sync_settings: SyncSetti
|
|||
"is_rbac_permissions_enabled",
|
||||
"is_grafana_incident_enabled",
|
||||
"is_grafana_labels_enabled",
|
||||
"is_grafana_irm_enabled",
|
||||
"grafana_incident_backend_url",
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from apps.base.models import UserNotificationPolicy, UserNotificationPolicyLogRe
|
|||
from apps.schedules.models import OnCallScheduleICal, OnCallScheduleWeb
|
||||
from apps.telegram.models import TelegramMessage
|
||||
from apps.user_management.models import Organization
|
||||
from common.constants.plugin_ids import PluginID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
@ -276,3 +277,16 @@ def test_get_notifiable_direct_paging_integrations(
|
|||
make_channel_filter(arc, is_default=False)
|
||||
notifiable_direct_paging_integrations = _assert(org, arc)
|
||||
assert notifiable_direct_paging_integrations.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"is_grafana_irm_enabled,expected",
|
||||
[
|
||||
(True, PluginID.IRM),
|
||||
(False, PluginID.ONCALL),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_active_ui_plugin_id(make_organization, is_grafana_irm_enabled, expected):
|
||||
org = make_organization(is_grafana_irm_enabled=is_grafana_irm_enabled)
|
||||
assert org.active_ui_plugin_id == expected
|
||||
|
|
|
|||
|
|
@ -587,6 +587,7 @@ def test_apply_sync_data_none_values(make_organization):
|
|||
grafana_token=organization.api_token,
|
||||
oncall_token=organization.gcom_token,
|
||||
grafana_url=organization.grafana_url,
|
||||
irm_enabled=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue