Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin) Add admin relay project config view [TET-509] #45120

Merged
merged 5 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/sentry/api/endpoints/admin_project_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from django.http import Http404
from rest_framework.request import Request
from rest_framework.response import Response

from sentry.api.base import Endpoint, pending_silo_endpoint
from sentry.api.permissions import SuperuserPermission
from sentry.models import Project
from sentry.relay import projectconfig_cache


@pending_silo_endpoint
class AdminRelayProjectConfigsEndpoint(Endpoint):
private = True
permission_classes = (SuperuserPermission,)

def get(self, request: Request) -> Response:
project_id = request.GET.get("projectId")

project_keys = []
if project_id is not None:
try:
project = Project.objects.get_from_cache(id=project_id)
for project_key in project.key_set.all():
project_keys.append(project_key.public_key)

except Exception:
raise Http404
RaduW marked this conversation as resolved.
Show resolved Hide resolved

project_key = request.GET.get("projectKey")
if project_key is not None:
project_keys.append(project_key)

configs = {}
for key in project_keys:
cached_config = projectconfig_cache.get(key)
if cached_config is not None:
configs[key] = cached_config
else:
configs[key] = None

# TODO if we don't think we'll add anything to the endpoint
# we may as well return just the configs
return Response({"configs": configs}, status=200)
6 changes: 6 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@

from .endpoints.accept_organization_invite import AcceptOrganizationInvite
from .endpoints.accept_project_transfer import AcceptProjectTransferEndpoint
from .endpoints.admin_project_configs import AdminRelayProjectConfigsEndpoint
from .endpoints.api_application_details import ApiApplicationDetailsEndpoint
from .endpoints.api_applications import ApiApplicationsEndpoint
from .endpoints.api_authorizations import ApiAuthorizationsEndpoint
Expand Down Expand Up @@ -2485,6 +2486,11 @@
url(r"^packages/$", InternalPackagesEndpoint.as_view()),
url(r"^environment/$", InternalEnvironmentEndpoint.as_view()),
url(r"^mail/$", InternalMailEndpoint.as_view()),
url(
r"^project-config/$",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priscilawebdev you have to update UI too.

AdminRelayProjectConfigsEndpoint.as_view(),
name="sentry-api-0-internal-project-config",
),
]
),
),
Expand Down
139 changes: 139 additions & 0 deletions tests/sentry/api/endpoints/test_admin_project_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from urllib import parse

from django.urls import reverse
from rest_framework import status

from sentry.relay import projectconfig_cache
from sentry.testutils import APITestCase
from sentry.testutils.silo import no_silo_test


@no_silo_test
class AdminRelayProjectConfigsEndpointTest(APITestCase):
def setUp(self):
super().setUp()
self.owner = self.create_user(
email="[email protected]", is_superuser=False, is_staff=True, is_active=True
)
self.org = self.create_organization(owner=self.owner)
self.first_team = self.create_team(organization=self.org)
self.proj1 = self.create_project(
name="proj1", organization=self.org, teams=[self.first_team]
)
self.proj2 = self.create_project(
name="proj2", organization=self.org, teams=[self.first_team]
)
self.superuser = self.create_user(
"[email protected]", is_superuser=True, is_staff=True, is_active=True
)
self.path = "sentry-api-0-internal-project-config"

self.p1_pk = self.create_project_key(self.proj1)
self.p2_pk = self.create_project_key(self.proj2)

projectconfig_cache.set_many(
{
self.p1_pk.public_key: "proj1 config",
}
)

def get_url(self, proj_id=None, key=None):
query = {}
if proj_id is not None:
query["projectId"] = proj_id
if key is not None:
query["projectKey"] = key

query_string = parse.urlencode(query)

ret_val = reverse(self.path)
ret_val += f"?{query_string}"
return ret_val

def test_normal_users_do_not_have_access(self):
"""
Request denied for non super-users
"""
self.login_as(self.owner)

url = self.get_url(proj_id=self.proj1.id)
response = self.client.get(url)

assert response.status_code == status.HTTP_403_FORBIDDEN

def test_retrieving_project_configs(self):
"""
Asking for a project will return all project configs from all public
keys in redis
"""
self.login_as(self.superuser, superuser=True)

url = self.get_url(proj_id=self.proj1.id)
response = self.client.get(url)

assert response.status_code == status.HTTP_200_OK
expected = {"configs": {self.p1_pk.public_key: "proj1 config"}}
actual = response.json()
assert actual == expected

def test_retrieving_public_key_configs(self):
"""
Asking for a particular public key will return only the project config
for that public key
"""
self.login_as(self.superuser, superuser=True)

url = self.get_url(key=self.p1_pk.public_key)
response = self.client.get(url)

assert response.status_code == status.HTTP_200_OK
expected = {"configs": {self.p1_pk.public_key: "proj1 config"}}
actual = response.json()
assert actual == expected

def test_uncached_project(self):
"""
Asking for a project that was not cached in redis will return
an empty marker
"""
expected = {"configs": {self.p2_pk.public_key: None}}

self.login_as(self.superuser, superuser=True)

url = self.get_url(proj_id=self.proj2.id)
response = self.client.get(url)
assert response.status_code == status.HTTP_200_OK
actual = response.json()
assert actual == expected

url = self.get_url(key=self.p2_pk.public_key)
response = self.client.get(url)
assert response.status_code == status.HTTP_200_OK
actual = response.json()
assert actual == expected

def test_inexistent_project(self):
"""
Asking for an inexitent project will return 404
"""
inexistent_project_id = 2 ^ 32
self.login_as(self.superuser, superuser=True)

url = self.get_url(proj_id=inexistent_project_id)
response = self.client.get(url)
assert response.status_code == status.HTTP_404_NOT_FOUND

def test_inexistent_key(self):
"""
Asking for an inexistent project key will return an empty result
"""
inexsitent_key = 123
self.login_as(self.superuser, superuser=True)

url = self.get_url(key=inexsitent_key)
response = self.client.get(url)

assert response.status_code == status.HTTP_200_OK
expected = {"configs": {str(inexsitent_key): None}}
actual = response.json()
assert actual == expected