2022-06-03 08:09:47 -06:00
|
|
|
|
import logging
|
2024-07-31 13:12:56 -03:00
|
|
|
|
import typing
|
2024-01-04 12:34:28 -03:00
|
|
|
|
import uuid
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
|
from celery.utils.log import get_task_logger
|
2022-09-02 14:06:42 -06:00
|
|
|
|
from django.conf import settings
|
2022-06-03 08:09:47 -06:00
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
|
2024-07-31 13:12:56 -03:00
|
|
|
|
from apps.alerts.models import AlertReceiveChannel
|
|
|
|
|
|
from apps.api.permissions import LegacyAccessControlRole
|
|
|
|
|
|
from apps.auth_token.exceptions import InvalidToken
|
|
|
|
|
|
from apps.grafana_plugin.helpers.client import GcomAPIClient, GCOMInstanceInfo, GrafanaAPIClient
|
|
|
|
|
|
from apps.grafana_plugin.sync_data import SyncData, SyncPermission, SyncSettings, SyncTeam, SyncUser
|
|
|
|
|
|
from apps.metrics_exporter.helpers import metrics_bulk_update_team_label_cache
|
|
|
|
|
|
from apps.metrics_exporter.metrics_cache_manager import MetricsCacheManager
|
2022-06-03 08:09:47 -06:00
|
|
|
|
from apps.user_management.models import Organization, Team, User
|
2024-01-04 12:34:28 -03:00
|
|
|
|
from common.utils import task_lock
|
2024-07-31 13:12:56 -03:00
|
|
|
|
from settings.base import CLOUD_LICENSE_NAME, OPEN_SOURCE_LICENSE_NAME
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
|
logger = get_task_logger(__name__)
|
|
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def sync_organization(organization: Organization) -> None:
|
2024-01-04 12:34:28 -03:00
|
|
|
|
# ensure one sync task is running at most for a given org at a given time
|
|
|
|
|
|
lock_id = "sync-organization-lock-{}".format(organization.id)
|
|
|
|
|
|
random_value = str(uuid.uuid4())
|
|
|
|
|
|
with task_lock(lock_id, random_value) as acquired:
|
|
|
|
|
|
if acquired:
|
|
|
|
|
|
_sync_organization(organization)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# sync already running
|
|
|
|
|
|
logger.info(f"Sync for Organization {organization.pk} already in progress.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _sync_organization(organization: Organization) -> None:
|
Revert "Refactor gcom api calls when syncing org" (#3498)
Reverts grafana/oncall#3489
Reviewing logs, it seems something broke related to [token
auth](https://ops.grafana-ops.net/explore?schemaVersion=1&panes=%7B%22ffS%22:%7B%22datasource%22:%22OP27Xzxnk%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%7Bcluster%3D~%5C%22dev-.%2A%5C%22,%20namespace%3D%5C%22grafana-com%5C%22,%20job%3D%5C%22grafana-com%2Fgrafana-com-api%5C%22%7D%20%7C~%20%5C%22%2Finstances%2F%5Ba-z0-9%5D%2B.config%3Dtrue%5C%22%20%7C%3D%20%5C%22Grafana%20OnCall%5C%22%22,%22editorMode%22:%22code%22,%22queryType%22:%22range%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22OP27Xzxnk%22%7D%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D%7D&orgId=1).
Reverting for now, will revisit in a later PR.
2023-12-04 15:02:58 -03:00
|
|
|
|
grafana_api_client = GrafanaAPIClient(api_url=organization.grafana_url, api_token=organization.api_token)
|
2024-07-29 17:28:35 +01:00
|
|
|
|
gcom_client = GcomAPIClient(settings.GRAFANA_COM_ADMIN_API_TOKEN)
|
|
|
|
|
|
|
2024-08-22 14:49:22 -03:00
|
|
|
|
# check organization API token is valid
|
|
|
|
|
|
_, check_token_call_status = grafana_api_client.check_token()
|
|
|
|
|
|
if not check_token_call_status["connected"]:
|
|
|
|
|
|
organization.api_token_status = Organization.API_TOKEN_STATUS_FAILED
|
|
|
|
|
|
organization.save(update_fields=["api_token_status"])
|
|
|
|
|
|
logger.warning(f"Sync not successful org={organization.pk} token_status=FAILED")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2024-07-31 13:12:56 -03:00
|
|
|
|
rbac_is_enabled = organization.is_rbac_permissions_enabled
|
2024-07-29 17:28:35 +01:00
|
|
|
|
# Update organization's RBAC status if it's an open-source instance, or it's an active cloud instance.
|
|
|
|
|
|
# Don't update non-active cloud instances (e.g. paused) as they can return 200 OK but not have RBAC enabled.
|
|
|
|
|
|
if settings.LICENSE == settings.OPEN_SOURCE_LICENSE_NAME or gcom_client.is_stack_active(organization.stack_id):
|
2024-07-31 13:12:56 -03:00
|
|
|
|
rbac_enabled_update, server_error = grafana_api_client.is_rbac_enabled_for_organization()
|
2024-07-29 17:28:35 +01:00
|
|
|
|
if not server_error: # Only update RBAC status if Grafana didn't return a server error
|
2024-07-31 13:12:56 -03:00
|
|
|
|
rbac_is_enabled = rbac_enabled_update
|
2022-11-29 09:41:56 +01:00
|
|
|
|
|
2024-07-31 13:12:56 -03:00
|
|
|
|
# get incident plugin settings
|
|
|
|
|
|
grafana_incident_settings, _ = grafana_api_client.get_grafana_incident_plugin_settings()
|
|
|
|
|
|
is_grafana_incident_enabled = False
|
|
|
|
|
|
grafana_incident_backend_url = None
|
|
|
|
|
|
if grafana_incident_settings is not None:
|
|
|
|
|
|
is_grafana_incident_enabled = grafana_incident_settings["enabled"]
|
|
|
|
|
|
grafana_incident_backend_url = (grafana_incident_settings.get("jsonData") or {}).get(
|
|
|
|
|
|
GrafanaAPIClient.GRAFANA_INCIDENT_PLUGIN_BACKEND_URL_KEY
|
|
|
|
|
|
)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
2024-07-31 13:12:56 -03:00
|
|
|
|
# get labels plugin settings
|
|
|
|
|
|
is_grafana_labels_enabled = False
|
|
|
|
|
|
grafana_labels_plugin_settings, _ = grafana_api_client.get_grafana_labels_plugin_settings()
|
|
|
|
|
|
if grafana_labels_plugin_settings is not None:
|
|
|
|
|
|
is_grafana_labels_enabled = grafana_labels_plugin_settings["enabled"]
|
|
|
|
|
|
|
2024-10-11 14:57:59 -04:00
|
|
|
|
# get IRM plugin settings
|
|
|
|
|
|
is_grafana_irm_enabled = False
|
2024-10-14 10:34:33 -03:00
|
|
|
|
grafana_irm_plugin_settings, _ = grafana_api_client.get_grafana_irm_plugin_settings()
|
2024-10-11 14:57:59 -04:00
|
|
|
|
if grafana_irm_plugin_settings is not None:
|
|
|
|
|
|
is_grafana_irm_enabled = grafana_irm_plugin_settings["enabled"]
|
|
|
|
|
|
|
2024-07-31 13:12:56 -03:00
|
|
|
|
oncall_api_url = settings.BASE_URL
|
|
|
|
|
|
if settings.LICENSE == CLOUD_LICENSE_NAME:
|
|
|
|
|
|
oncall_api_url = settings.GRAFANA_CLOUD_ONCALL_API_URL
|
|
|
|
|
|
|
|
|
|
|
|
sync_settings = SyncSettings(
|
|
|
|
|
|
stack_id=organization.stack_id,
|
|
|
|
|
|
org_id=organization.org_id,
|
|
|
|
|
|
license=settings.LICENSE,
|
|
|
|
|
|
oncall_api_url=oncall_api_url,
|
|
|
|
|
|
oncall_token=organization.gcom_token,
|
|
|
|
|
|
grafana_url=organization.grafana_url,
|
|
|
|
|
|
grafana_token=organization.api_token,
|
|
|
|
|
|
rbac_enabled=rbac_is_enabled,
|
|
|
|
|
|
incident_enabled=is_grafana_incident_enabled,
|
|
|
|
|
|
incident_backend_url=grafana_incident_backend_url,
|
|
|
|
|
|
labels_enabled=is_grafana_labels_enabled,
|
2024-10-11 14:57:59 -04:00
|
|
|
|
irm_enabled=is_grafana_irm_enabled,
|
2022-06-03 08:09:47 -06:00
|
|
|
|
)
|
2024-07-31 13:12:56 -03:00
|
|
|
|
_sync_organization_data(organization, sync_settings)
|
|
|
|
|
|
if organization.api_token_status == Organization.API_TOKEN_STATUS_OK:
|
|
|
|
|
|
sync_users_and_teams(grafana_api_client, organization)
|
2023-04-14 09:15:57 +02:00
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def _sync_instance_info(organization: Organization) -> None:
|
2022-12-12 18:25:56 +01:00
|
|
|
|
if organization.gcom_token:
|
|
|
|
|
|
gcom_client = GcomAPIClient(organization.gcom_token)
|
Revert "Refactor gcom api calls when syncing org" (#3498)
Reverts grafana/oncall#3489
Reviewing logs, it seems something broke related to [token
auth](https://ops.grafana-ops.net/explore?schemaVersion=1&panes=%7B%22ffS%22:%7B%22datasource%22:%22OP27Xzxnk%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%7Bcluster%3D~%5C%22dev-.%2A%5C%22,%20namespace%3D%5C%22grafana-com%5C%22,%20job%3D%5C%22grafana-com%2Fgrafana-com-api%5C%22%7D%20%7C~%20%5C%22%2Finstances%2F%5Ba-z0-9%5D%2B.config%3Dtrue%5C%22%20%7C%3D%20%5C%22Grafana%20OnCall%5C%22%22,%22editorMode%22:%22code%22,%22queryType%22:%22range%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22OP27Xzxnk%22%7D%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D%7D&orgId=1).
Reverting for now, will revisit in a later PR.
2023-12-04 15:02:58 -03:00
|
|
|
|
instance_info = gcom_client.get_instance_info(organization.stack_id)
|
2022-12-12 18:25:56 +01:00
|
|
|
|
|
|
|
|
|
|
if not instance_info or instance_info["orgId"] != organization.org_id:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
organization.stack_slug = instance_info["slug"]
|
|
|
|
|
|
organization.org_slug = instance_info["orgSlug"]
|
|
|
|
|
|
organization.org_title = instance_info["orgName"]
|
|
|
|
|
|
organization.region_slug = instance_info["regionSlug"]
|
|
|
|
|
|
organization.grafana_url = instance_info["url"]
|
2023-03-09 12:10:19 +08:00
|
|
|
|
organization.cluster_slug = instance_info["clusterSlug"]
|
2022-12-12 18:25:56 +01:00
|
|
|
|
organization.gcom_token_org_last_time_synced = timezone.now()
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def sync_users_and_teams(client: GrafanaAPIClient, organization: Organization) -> None:
|
2023-01-24 13:44:07 +08:00
|
|
|
|
sync_users(client, organization)
|
|
|
|
|
|
sync_teams(client, organization)
|
|
|
|
|
|
sync_team_members(client, organization)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def sync_users(client: GrafanaAPIClient, organization: Organization, **kwargs) -> None:
|
2023-01-24 13:44:07 +08:00
|
|
|
|
api_users = client.get_users(organization.is_rbac_permissions_enabled, **kwargs)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
# check if api_users are shaped correctly. e.g. for paused instance, the response is not a list.
|
|
|
|
|
|
if not api_users or not isinstance(api_users, (tuple, list)):
|
|
|
|
|
|
return
|
2024-07-31 13:12:56 -03:00
|
|
|
|
|
|
|
|
|
|
sync_users = [
|
|
|
|
|
|
SyncUser(
|
|
|
|
|
|
id=user["userId"],
|
|
|
|
|
|
name=user["name"],
|
|
|
|
|
|
login=user["login"],
|
|
|
|
|
|
email=user["email"],
|
|
|
|
|
|
role=user["role"],
|
|
|
|
|
|
avatar_url=user["avatarUrl"],
|
|
|
|
|
|
teams=None,
|
2024-07-31 17:27:42 -03:00
|
|
|
|
permissions=[SyncPermission(action=permission["action"]) for permission in user["permissions"]],
|
2024-07-31 13:12:56 -03:00
|
|
|
|
)
|
|
|
|
|
|
for user in api_users
|
|
|
|
|
|
]
|
|
|
|
|
|
_sync_users_data(organization, sync_users, delete_extra=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
2023-01-24 13:44:07 +08:00
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def sync_teams(client: GrafanaAPIClient, organization: Organization, **kwargs) -> None:
|
2023-01-24 13:44:07 +08:00
|
|
|
|
api_teams_result, _ = client.get_teams(**kwargs)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
if not api_teams_result:
|
|
|
|
|
|
return
|
|
|
|
|
|
api_teams = api_teams_result["teams"]
|
2024-07-31 13:12:56 -03:00
|
|
|
|
sync_teams = [
|
|
|
|
|
|
SyncTeam(
|
|
|
|
|
|
team_id=team["id"],
|
|
|
|
|
|
name=team["name"],
|
|
|
|
|
|
email=team["email"],
|
|
|
|
|
|
avatar_url=team["avatarUrl"],
|
|
|
|
|
|
)
|
|
|
|
|
|
for team in api_teams
|
|
|
|
|
|
]
|
|
|
|
|
|
_sync_teams_data(organization, sync_teams)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
2023-01-24 13:44:07 +08:00
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def sync_team_members(client: GrafanaAPIClient, organization: Organization) -> None:
|
2024-07-31 13:12:56 -03:00
|
|
|
|
team_members = {}
|
2022-06-03 08:09:47 -06:00
|
|
|
|
for team in organization.teams.all():
|
|
|
|
|
|
members, _ = client.get_team_members(team.team_id)
|
|
|
|
|
|
if not members:
|
|
|
|
|
|
continue
|
2024-07-31 13:12:56 -03:00
|
|
|
|
team_members[team.team_id] = [member["userId"] for member in members]
|
|
|
|
|
|
_sync_teams_members_data(organization, team_members)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def delete_organization_if_needed(organization: Organization) -> bool:
|
2022-09-02 14:06:42 -06:00
|
|
|
|
# Organization has a manually set API token, it will not be found within GCOM
|
|
|
|
|
|
# and would need to be deleted manually.
|
2023-07-25 10:43:23 +01:00
|
|
|
|
from apps.auth_token.models import PluginAuthToken
|
|
|
|
|
|
|
2023-05-17 06:56:57 -06:00
|
|
|
|
manually_provisioned_token = PluginAuthToken.objects.filter(organization_id=organization.pk).first()
|
|
|
|
|
|
if manually_provisioned_token:
|
|
|
|
|
|
logger.info(f"Organization {organization.pk} has PluginAuthToken. Probably it's needed to delete org manually.")
|
2022-06-03 08:09:47 -06:00
|
|
|
|
return False
|
|
|
|
|
|
|
2022-09-02 14:06:42 -06:00
|
|
|
|
# Use common token as organization.gcom_token could be already revoked
|
2023-05-17 06:56:57 -06:00
|
|
|
|
client = GcomAPIClient(settings.GRAFANA_COM_ADMIN_API_TOKEN)
|
2022-09-02 14:06:42 -06:00
|
|
|
|
is_stack_deleted = client.is_stack_deleted(organization.stack_id)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
if not is_stack_deleted:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
organization.delete()
|
|
|
|
|
|
return True
|
2022-09-02 14:06:42 -06:00
|
|
|
|
|
|
|
|
|
|
|
2023-08-03 11:43:03 +02:00
|
|
|
|
def cleanup_organization(organization_pk: int) -> None:
|
2022-09-02 14:06:42 -06:00
|
|
|
|
logger.info(f"Start cleanup Organization {organization_pk}")
|
|
|
|
|
|
try:
|
2024-03-04 12:45:01 -07:00
|
|
|
|
organization = Organization.objects_with_deleted.get(pk=organization_pk)
|
|
|
|
|
|
|
|
|
|
|
|
from apps.grafana_plugin.tasks.sync import cleanup_empty_deleted_integrations
|
|
|
|
|
|
|
|
|
|
|
|
cleanup_empty_deleted_integrations.apply_async(
|
|
|
|
|
|
(
|
|
|
|
|
|
organization.pk,
|
|
|
|
|
|
False,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2022-09-02 14:06:42 -06:00
|
|
|
|
if delete_organization_if_needed(organization):
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"Deleting organization due to stack deletion. "
|
2022-09-08 11:56:04 -06:00
|
|
|
|
f"pk: {organization_pk}, stack_id: {organization.stack_id}, org_id: {organization.org_id}"
|
2022-09-02 14:06:42 -06:00
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.info(f"Organization {organization_pk} not deleted in gcom, no action taken")
|
2024-03-04 12:45:01 -07:00
|
|
|
|
|
2022-09-02 14:06:42 -06:00
|
|
|
|
except Organization.DoesNotExist:
|
|
|
|
|
|
logger.info(f"Organization {organization_pk} was not found")
|
2024-07-31 13:12:56 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _create_cloud_organization(
|
|
|
|
|
|
org_id: int, stack_id: int, sync_data: SyncData, instance_info: GCOMInstanceInfo
|
|
|
|
|
|
) -> Organization:
|
|
|
|
|
|
client = GcomAPIClient(sync_data.settings.oncall_token)
|
|
|
|
|
|
if not instance_info:
|
|
|
|
|
|
instance_info = client.get_instance_info(stack_id)
|
|
|
|
|
|
if not instance_info or str(instance_info["orgId"]) != org_id:
|
|
|
|
|
|
raise InvalidToken
|
|
|
|
|
|
|
|
|
|
|
|
return Organization.objects.create(
|
|
|
|
|
|
stack_id=str(instance_info["id"]),
|
|
|
|
|
|
stack_slug=instance_info["slug"],
|
|
|
|
|
|
grafana_url=instance_info["url"],
|
|
|
|
|
|
org_id=str(instance_info["orgId"]),
|
|
|
|
|
|
org_slug=instance_info["orgSlug"],
|
|
|
|
|
|
org_title=instance_info["orgName"],
|
|
|
|
|
|
region_slug=instance_info["regionSlug"],
|
|
|
|
|
|
cluster_slug=instance_info["clusterSlug"],
|
|
|
|
|
|
api_token=sync_data.settings.grafana_token,
|
|
|
|
|
|
gcom_token=sync_data.settings.oncall_token,
|
|
|
|
|
|
is_rbac_permissions_enabled=sync_data.settings.rbac_enabled,
|
|
|
|
|
|
defaults={"gcom_token_org_last_time_synced": timezone.now()},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _create_oss_organization(sync_data: SyncData) -> Organization:
|
|
|
|
|
|
return Organization.objects.create(
|
|
|
|
|
|
stack_id=settings.SELF_HOSTED_SETTINGS["STACK_ID"],
|
|
|
|
|
|
stack_slug=settings.SELF_HOSTED_SETTINGS["STACK_SLUG"],
|
|
|
|
|
|
org_id=settings.SELF_HOSTED_SETTINGS["ORG_ID"],
|
|
|
|
|
|
org_slug=settings.SELF_HOSTED_SETTINGS["ORG_SLUG"],
|
|
|
|
|
|
org_title=settings.SELF_HOSTED_SETTINGS["ORG_TITLE"],
|
|
|
|
|
|
region_slug=settings.SELF_HOSTED_SETTINGS["REGION_SLUG"],
|
|
|
|
|
|
cluster_slug=settings.SELF_HOSTED_SETTINGS["CLUSTER_SLUG"],
|
|
|
|
|
|
grafana_url=sync_data.settings.grafana_url,
|
|
|
|
|
|
api_token=sync_data.settings.grafana_token,
|
|
|
|
|
|
is_rbac_permissions_enabled=sync_data.settings.rbac_enabled,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _create_organization(
|
|
|
|
|
|
org_id: int, stack_id: int, sync_data: SyncData, instance_info: GCOMInstanceInfo
|
|
|
|
|
|
) -> typing.Optional[Organization]:
|
|
|
|
|
|
if settings.LICENSE == CLOUD_LICENSE_NAME:
|
|
|
|
|
|
return _create_cloud_organization(org_id, stack_id, sync_data, instance_info)
|
|
|
|
|
|
elif settings.LICENSE == OPEN_SOURCE_LICENSE_NAME:
|
|
|
|
|
|
return _create_oss_organization(sync_data)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_or_create_organization(
|
|
|
|
|
|
org_id: int, stack_id: int, sync_data: SyncData = None, instance_info: GCOMInstanceInfo = None
|
|
|
|
|
|
) -> Organization:
|
|
|
|
|
|
organization = Organization.objects.filter(org_id=org_id, stack_id=stack_id).first()
|
|
|
|
|
|
if not organization:
|
|
|
|
|
|
organization = _create_organization(org_id, stack_id, sync_data, instance_info)
|
|
|
|
|
|
return organization
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_or_create_user(organization: Organization, sync_user: SyncUser) -> User:
|
|
|
|
|
|
_sync_users_data(organization, [sync_user], delete_extra=False)
|
|
|
|
|
|
user = organization.users.get(user_id=sync_user.id)
|
|
|
|
|
|
|
|
|
|
|
|
# update team membership if needed
|
|
|
|
|
|
# (not removing user from teams, assuming this is called on user creation/first login only;
|
|
|
|
|
|
# periodic sync will keep teams updated)
|
|
|
|
|
|
membership = sync_user.teams or []
|
|
|
|
|
|
for team_id in membership:
|
|
|
|
|
|
team = organization.teams.filter(team_id=team_id).first()
|
|
|
|
|
|
if team:
|
|
|
|
|
|
user.teams.add(team)
|
|
|
|
|
|
|
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _sync_organization_data(organization: Organization, sync_settings: SyncSettings):
|
|
|
|
|
|
organization.is_rbac_permissions_enabled = sync_settings.rbac_enabled
|
|
|
|
|
|
logger.info(f"RBAC status org={organization.pk} rbac_enabled={organization.is_rbac_permissions_enabled}")
|
|
|
|
|
|
|
2024-10-11 14:57:59 -04:00
|
|
|
|
organization.is_grafana_irm_enabled = sync_settings.irm_enabled
|
2024-07-31 13:12:56 -03:00
|
|
|
|
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
|
|
|
|
|
|
organization.grafana_url = sync_settings.grafana_url
|
|
|
|
|
|
organization.api_token = sync_settings.grafana_token
|
|
|
|
|
|
organization.last_time_synced = timezone.now()
|
|
|
|
|
|
|
|
|
|
|
|
_sync_instance_info(organization)
|
|
|
|
|
|
|
|
|
|
|
|
grafana_api_client = GrafanaAPIClient(api_url=organization.grafana_url, api_token=organization.api_token)
|
|
|
|
|
|
_, check_token_call_status = grafana_api_client.check_token()
|
|
|
|
|
|
if check_token_call_status["connected"]:
|
|
|
|
|
|
organization.api_token_status = Organization.API_TOKEN_STATUS_OK
|
|
|
|
|
|
organization.last_time_synced = timezone.now()
|
|
|
|
|
|
else:
|
|
|
|
|
|
organization.api_token_status = Organization.API_TOKEN_STATUS_FAILED
|
|
|
|
|
|
logger.warning(f"Sync not successful org={organization.pk} token_status=FAILED")
|
|
|
|
|
|
|
|
|
|
|
|
organization.save(
|
|
|
|
|
|
update_fields=[
|
|
|
|
|
|
"api_token",
|
|
|
|
|
|
"api_token_status",
|
|
|
|
|
|
"cluster_slug",
|
|
|
|
|
|
"stack_slug",
|
|
|
|
|
|
"org_slug",
|
|
|
|
|
|
"org_title",
|
|
|
|
|
|
"region_slug",
|
|
|
|
|
|
"grafana_url",
|
|
|
|
|
|
"last_time_synced",
|
|
|
|
|
|
"gcom_token_org_last_time_synced",
|
|
|
|
|
|
"is_rbac_permissions_enabled",
|
|
|
|
|
|
"is_grafana_incident_enabled",
|
|
|
|
|
|
"is_grafana_labels_enabled",
|
2024-10-11 14:57:59 -04:00
|
|
|
|
"is_grafana_irm_enabled",
|
2024-07-31 13:12:56 -03:00
|
|
|
|
"grafana_incident_backend_url",
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _sync_users_data(organization: Organization, sync_users: list[SyncUser], delete_extra=False):
|
2024-12-12 19:11:59 -03:00
|
|
|
|
if sync_users is None:
|
|
|
|
|
|
return
|
2024-07-31 13:12:56 -03:00
|
|
|
|
users_to_sync = (
|
|
|
|
|
|
User(
|
|
|
|
|
|
organization_id=organization.pk,
|
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
|
email=user.email,
|
|
|
|
|
|
name=user.name,
|
|
|
|
|
|
username=user.login,
|
|
|
|
|
|
role=getattr(LegacyAccessControlRole, user.role.upper(), LegacyAccessControlRole.NONE),
|
|
|
|
|
|
avatar_url=user.avatar_url,
|
2024-08-02 15:08:45 -03:00
|
|
|
|
permissions=[{"action": permission.action} for permission in user.permissions] if user.permissions else [],
|
2024-07-31 13:12:56 -03:00
|
|
|
|
)
|
|
|
|
|
|
for user in sync_users
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
existing_user_ids = set(organization.users.all().values_list("user_id", flat=True))
|
|
|
|
|
|
kwargs = {}
|
|
|
|
|
|
if settings.DATABASE_TYPE in ("sqlite3", "postgresql"):
|
|
|
|
|
|
# unique_fields is required for sqlite and postgresql setups
|
|
|
|
|
|
kwargs["unique_fields"] = ("organization_id", "user_id", "is_active")
|
|
|
|
|
|
organization.users.bulk_create(
|
|
|
|
|
|
users_to_sync,
|
|
|
|
|
|
update_conflicts=True,
|
|
|
|
|
|
update_fields=("email", "name", "username", "role", "avatar_url", "permissions"),
|
|
|
|
|
|
batch_size=5000,
|
|
|
|
|
|
**kwargs,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Retrieve primary keys for the newly created users
|
|
|
|
|
|
#
|
|
|
|
|
|
# If the model’s primary key is an AutoField, the primary key attribute can only be retrieved
|
|
|
|
|
|
# on certain databases (currently PostgreSQL, MariaDB 10.5+, and SQLite 3.35+).
|
|
|
|
|
|
# On other databases, it will not be set.
|
|
|
|
|
|
# https://docs.djangoproject.com/en/4.1/ref/models/querysets/#django.db.models.query.QuerySet.bulk_create
|
|
|
|
|
|
created_users = organization.users.exclude(user_id__in=existing_user_ids)
|
|
|
|
|
|
|
|
|
|
|
|
if delete_extra:
|
|
|
|
|
|
# delete removed users
|
|
|
|
|
|
existing_user_ids |= set(u.user_id for u in created_users)
|
|
|
|
|
|
user_ids_to_delete = existing_user_ids - {user.id for user in sync_users}
|
|
|
|
|
|
organization.users.filter(user_id__in=user_ids_to_delete).delete()
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-08-02 15:08:45 -03:00
|
|
|
|
def _sync_teams_data(organization: Organization, sync_teams: list[SyncTeam] | None):
|
2024-07-31 13:12:56 -03:00
|
|
|
|
if sync_teams is None:
|
|
|
|
|
|
sync_teams = []
|
|
|
|
|
|
# keep existing team names mapping to check for possible metrics cache updates
|
|
|
|
|
|
existing_team_names = {team.team_id: team.name for team in organization.teams.all()}
|
|
|
|
|
|
teams_to_sync = tuple(
|
|
|
|
|
|
Team(
|
|
|
|
|
|
organization_id=organization.pk,
|
|
|
|
|
|
team_id=team.team_id,
|
|
|
|
|
|
name=team.name,
|
|
|
|
|
|
email=team.email,
|
|
|
|
|
|
avatar_url=team.avatar_url,
|
|
|
|
|
|
)
|
|
|
|
|
|
for team in sync_teams
|
|
|
|
|
|
)
|
|
|
|
|
|
# create entries, update if team_id already exists in the organization
|
|
|
|
|
|
kwargs = {}
|
|
|
|
|
|
if settings.DATABASE_TYPE in ("sqlite3", "postgresql"):
|
|
|
|
|
|
# unique_fields is required for sqlite and postgresql setups
|
|
|
|
|
|
kwargs["unique_fields"] = ("organization_id", "team_id")
|
|
|
|
|
|
organization.teams.bulk_create(
|
|
|
|
|
|
teams_to_sync,
|
|
|
|
|
|
batch_size=5000,
|
|
|
|
|
|
update_conflicts=True,
|
|
|
|
|
|
update_fields=("name", "email", "avatar_url"),
|
|
|
|
|
|
**kwargs,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# create missing direct paging integrations
|
|
|
|
|
|
AlertReceiveChannel.objects.create_missing_direct_paging_integrations(organization)
|
|
|
|
|
|
|
|
|
|
|
|
# delete removed teams and their direct paging integrations
|
|
|
|
|
|
existing_team_ids = set(organization.teams.all().values_list("team_id", flat=True))
|
|
|
|
|
|
team_ids_to_delete = existing_team_ids - set(t.team_id for t in sync_teams)
|
|
|
|
|
|
organization.alert_receive_channels.filter(
|
|
|
|
|
|
team__team_id__in=team_ids_to_delete, integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING
|
|
|
|
|
|
).delete()
|
|
|
|
|
|
organization.teams.filter(team_id__in=team_ids_to_delete).delete()
|
|
|
|
|
|
|
|
|
|
|
|
# collect teams diffs to update metrics cache
|
|
|
|
|
|
metrics_teams_to_update: MetricsCacheManager.TeamsDiffMap = {}
|
|
|
|
|
|
for team_id in team_ids_to_delete:
|
|
|
|
|
|
metrics_teams_to_update = MetricsCacheManager.update_team_diff(metrics_teams_to_update, team_id, deleted=True)
|
|
|
|
|
|
for team in sync_teams:
|
|
|
|
|
|
previous_name = existing_team_names.get(team.team_id)
|
|
|
|
|
|
if previous_name and previous_name != team.name:
|
|
|
|
|
|
metrics_teams_to_update = MetricsCacheManager.update_team_diff(
|
|
|
|
|
|
metrics_teams_to_update, team.team_id, new_name=team.name
|
|
|
|
|
|
)
|
|
|
|
|
|
metrics_bulk_update_team_label_cache(metrics_teams_to_update, organization.id)
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-08-02 15:08:45 -03:00
|
|
|
|
def _sync_teams_members_data(organization: Organization, team_members: dict[int, list[int]] | None):
|
|
|
|
|
|
if team_members is None:
|
|
|
|
|
|
return
|
2024-07-31 13:12:56 -03:00
|
|
|
|
# set team members
|
|
|
|
|
|
for team_id, members_ids in team_members.items():
|
|
|
|
|
|
team = organization.teams.get(team_id=team_id)
|
2024-08-16 18:43:52 +02:00
|
|
|
|
if members_ids:
|
|
|
|
|
|
team.users.set(organization.users.filter(user_id__in=members_ids))
|
|
|
|
|
|
else:
|
|
|
|
|
|
team.users.clear()
|
2024-07-31 13:12:56 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_sync_data(organization: Organization, sync_data: SyncData):
|
|
|
|
|
|
# update org + settings
|
|
|
|
|
|
_sync_organization_data(organization, sync_data.settings)
|
|
|
|
|
|
# update or create users
|
|
|
|
|
|
_sync_users_data(organization, sync_data.users, delete_extra=True)
|
|
|
|
|
|
# update or create teams + direct paging integrations
|
|
|
|
|
|
_sync_teams_data(organization, sync_data.teams)
|
|
|
|
|
|
# update team members
|
|
|
|
|
|
_sync_teams_members_data(organization, sync_data.team_members)
|