Browse Source

chore(replay): delete remote config files (#78623)

relates to https://github.com/getsentry/team-replay/issues/456
Michelle Zhang 5 months ago
parent
commit
cbb15373c7

+ 0 - 6
.github/CODEOWNERS

@@ -323,12 +323,6 @@ tests/sentry/api/endpoints/test_organization_dashboard_widget_details.py     @ge
 ## End of Profiling
 
 
-## Configurations
-/src/sentry/remote_config/              @getsentry/replay-backend
-/tests/sentry/remote_config/            @getsentry/replay-backend
-## End of Configurations
-
-
 ## Flags
 /src/sentry/flags/              @getsentry/replay-backend
 /tests/sentry/flags/            @getsentry/replay-backend

+ 0 - 1
src/sentry/api/api_owners.py

@@ -28,4 +28,3 @@ class ApiOwner(Enum):
     TELEMETRY_EXPERIENCE = "telemetry-experience"
     UNOWNED = "unowned"
     WEB_FRONTEND_SDKS = "team-web-sdk-frontend"
-    REMOTE_CONFIG = "replay-backend"

+ 0 - 14
src/sentry/api/urls.py

@@ -250,10 +250,6 @@ from sentry.monitors.endpoints.project_processing_errors_details import (
 from sentry.monitors.endpoints.project_processing_errors_index import (
     ProjectProcessingErrorsIndexEndpoint,
 )
-from sentry.remote_config.endpoints import (
-    ProjectConfigurationEndpoint,
-    ProjectConfigurationProxyEndpoint,
-)
 from sentry.replays.endpoints.organization_replay_count import OrganizationReplayCountEndpoint
 from sentry.replays.endpoints.organization_replay_details import OrganizationReplayDetailsEndpoint
 from sentry.replays.endpoints.organization_replay_events_meta import (
@@ -2441,11 +2437,6 @@ PROJECT_URLS: list[URLPattern | URLResolver] = [
         r"^(?P<organization_id_or_slug>[^\/]+)/(?P<project_id_or_slug>[^\/]+)/keys/(?P<key_id>[^\/]+)/stats/$",
         ProjectKeyStatsEndpoint.as_view(),
     ),
-    re_path(
-        r"^(?P<organization_id_or_slug>[^\/]+)/(?P<project_id_or_slug>[^\/]+)/configuration/$",
-        ProjectConfigurationEndpoint.as_view(),
-        name="sentry-api-0-project-key-configuration",
-    ),
     re_path(
         r"^(?P<organization_id_or_slug>[^/]+)/(?P<project_id_or_slug>[^/]+)/members/$",
         ProjectMemberIndexEndpoint.as_view(),
@@ -3301,11 +3292,6 @@ urlpatterns = [
         SetupWizard.as_view(),
         name="sentry-api-0-project-wizard",
     ),
-    re_path(
-        r"^remote-config/projects/(?P<project_id>[^\/]+)/$",
-        ProjectConfigurationProxyEndpoint.as_view(),
-        name="sentry-api-0-project-remote-configuration",
-    ),
     # Internal
     re_path(
         r"^internal/",

+ 0 - 1
src/sentry/remote_config/README.md

@@ -1 +0,0 @@
-# Remote Configuration Product

+ 0 - 0
src/sentry/remote_config/__init__.py


+ 0 - 157
src/sentry/remote_config/docs/api.md

@@ -1,157 +0,0 @@
-# Configurations API
-
-Host: https://sentry.io/api/0
-
-**Authors.**
-
-@cmanallen
-
-## Configuration [/projects/<organization_id_or_slug>/<project_id_or_slug>/configuration/]
-
-### Get Configuration [GET]
-
-Retrieve the project's configuration.
-
-**Attributes**
-
-| Column   | Type           | Description                                   |
-| -------- | -------------- | --------------------------------------------- |
-| features | array[Feature] | Custom, user-defined configuration container. |
-| options  | Option         | Sentry SDK options container.                 |
-
-**Feature Object**
-
-| Field | Type   | Description                        |
-| ----- | ------ | ---------------------------------- |
-| key   | string | The name used to lookup a feature. |
-| value | any    | A JSON value.                      |
-
-**Option Object**
-
-| Field              | Type  | Description                                         |
-| ------------------ | ----- | --------------------------------------------------- |
-| sample_rate        | float | Error sample rate. A numeric value between 0 and 1. |
-| traces_sample_rate | float | Trace sample rate. A numeric value between 0 and 1. |
-
-**If an existing configuration exists**
-
-- Response 200
-
-  ```json
-  {
-    "data": {
-      "features": [
-        {
-          "key": "hello",
-          "value": "world"
-        },
-        {
-          "key": "has_access",
-          "value": true
-        }
-      ],
-      "options": {
-        "sample_rate": 1.0,
-        "traces_sample_rate": 0.5
-      }
-    }
-  }
-  ```
-
-**If no existing configuration exists**
-
-- Response 404
-
-### Set Configuration [POST]
-
-Set the project's configuration.
-
-- Request
-
-  ```json
-  {
-    "data": {
-      "features": [
-        {
-          "key": "hello",
-          "value": "world"
-        },
-        {
-          "key": "has_access",
-          "value": true
-        }
-      ],
-      "options": {
-        "sample_rate": 1.0,
-        "traces_sample_rate": 0.5
-      }
-    }
-  }
-  ```
-
-- Response 201
-
-  ```json
-  {
-    "data": {
-      "features": [
-        {
-          "key": "hello",
-          "value": "world"
-        },
-        {
-          "key": "has_access",
-          "value": true
-        }
-      ],
-      "options": {
-        "sample_rate": 1.0,
-        "traces_sample_rate": 0.5
-      }
-    }
-  }
-  ```
-
-### Delete Configuration [DELETE]
-
-Delete the project's configuration.
-
-- Response 204
-
-## Configuration Proxy [/remote-config/projects/<project_id>/]
-
-Temporary configuration proxy resource.
-
-### Get Configuration [GET]
-
-Fetch a project's configuration. Responses should be proxied exactly to the SDK.
-
-- Response 200
-
-  - Headers
-
-    Cache-Control: public, max-age=3600
-    Content-Type: application/json
-    ETag: a7966bf58e23583c9a5a4059383ff850
-
-  - Body
-
-    ```json
-    {
-      "features": [
-        {
-          "key": "hello",
-          "value": "world"
-        },
-        {
-          "key": "has_access",
-          "value": true
-        }
-      ],
-      "options": {
-        "sample_rate": 1.0,
-        "traces_sample_rate": 0.5
-      },
-      "version": 1
-    }
-    ```

+ 0 - 106
src/sentry/remote_config/docs/protocol.md

@@ -1,106 +0,0 @@
-# Remote Configuration Protocol
-
-Host: https://o1300299.ingest.us.sentry.io
-
-**Authors.**
-
-@cmanallen
-
-## Configuration [/api/<project_id>/configuration/]
-
-### Get Configuration [GET]
-
-Retrieve a project's configuration.
-
-**Attributes**
-
-| Field    | Type           | Description                                   |
-| -------- | -------------- | --------------------------------------------- |
-| features | array[Feature] | Custom, user-defined configuration container. |
-| options  | Option         | Sentry SDK options container.                 |
-| version  | number         | The version of the protocol.                  |
-
-**Feature Object**
-
-| Field | Type   | Description                        |
-| ----- | ------ | ---------------------------------- |
-| key   | string | The name used to lookup a feature. |
-| value | any    | A JSON value.                      |
-
-**Option Object**
-
-| Field              | Type  | Description        |
-| ------------------ | ----- | ------------------ |
-| sample_rate        | float | Error sample rate. |
-| traces_sample_rate | float | Trace sample rate. |
-
-**Server ETag Matches**
-
-If the server's ETag matches the request's a 304 (NOT MODIFIED) response is returned.
-
-- Request
-
-  - Headers
-
-    Accept: application/json
-    If-None-Match: 8832040536272351350
-
-- Response 304
-
-  - Headers
-
-    Cache-Control: public, max-age=60
-    Content-Type: application/json
-    ETag: 8832040536272351350
-
-**Server ETag Does Not Match or If-None-Match Omitted**
-
-If the server's ETag does not match the request's a 200 response is returned.
-
-- Request
-
-  - Headers
-
-    Accept: application/json
-    If-None-Match: ABC
-
-- Response 200
-
-  - Headers
-
-    Cache-Control: public, max-age=60
-    Content-Type: application/json
-    ETag: 8832040536272351350
-
-  - Body
-
-    ```json
-    {
-      "features": [
-        {
-          "key": "hello",
-          "value": "world"
-        },
-        {
-          "key": "has_access",
-          "value": true
-        }
-      ],
-      "options": {
-        "sample_rate": 1.0,
-        "traces_sample_rate": 0.5
-      },
-      "version": 1
-    }
-    ```
-
-**No Configuration Exists for the Project**
-
-- Request
-
-  - Headers
-
-    Accept: application/json
-    If-None-Match: ABC
-
-- Response 404

+ 0 - 152
src/sentry/remote_config/endpoints.py

@@ -1,152 +0,0 @@
-import hashlib
-
-from django.contrib.auth.models import AnonymousUser
-from rest_framework import serializers
-from rest_framework.authentication import BasicAuthentication
-from rest_framework.request import Request
-from rest_framework.response import Response
-from rest_framework.serializers import Serializer
-
-from sentry import features
-from sentry.api.api_owners import ApiOwner
-from sentry.api.api_publish_status import ApiPublishStatus
-from sentry.api.authentication import AuthenticationSiloLimit
-from sentry.api.base import Endpoint, region_silo_endpoint
-from sentry.api.bases.project import ProjectEndpoint, ProjectEventPermission
-from sentry.api.permissions import RelayPermission
-from sentry.models.project import Project
-from sentry.remote_config.storage import make_api_backend, make_configuration_backend
-from sentry.silo.base import SiloMode
-from sentry.utils import json, metrics
-
-
-class OptionsValidator(Serializer):
-    sample_rate = serializers.FloatField(max_value=1.0, min_value=0, required=True)
-    traces_sample_rate = serializers.FloatField(max_value=1.0, min_value=0, required=True)
-
-
-class FeatureValidator(Serializer):
-    key = serializers.CharField(required=True)
-    value = serializers.JSONField(required=True, allow_null=True)
-
-
-class ConfigurationValidator(Serializer):
-    id = serializers.UUIDField(read_only=True)
-    features: serializers.ListSerializer = serializers.ListSerializer(
-        child=FeatureValidator(), required=True
-    )
-    options = OptionsValidator(required=True)
-
-
-class ConfigurationContainerValidator(Serializer):
-    data = ConfigurationValidator(required=True)  # type: ignore[assignment]
-
-
-@region_silo_endpoint
-class ProjectConfigurationEndpoint(ProjectEndpoint):
-    owner = ApiOwner.REMOTE_CONFIG
-    permission_classes = (ProjectEventPermission,)
-    publish_status = {
-        "GET": ApiPublishStatus.EXPERIMENTAL,
-        "POST": ApiPublishStatus.EXPERIMENTAL,
-        "DELETE": ApiPublishStatus.EXPERIMENTAL,
-    }
-
-    def get(self, request: Request, project: Project) -> Response:
-        """Get remote configuration from project options."""
-        if not features.has(
-            "organizations:remote-config", project.organization, actor=request.user
-        ):
-            return Response("Disabled", status=404)
-
-        remote_config, source = make_api_backend(project).get()
-        if remote_config is None:
-            return Response("Not found.", status=404)
-
-        return Response(
-            {"data": remote_config},
-            status=200,
-            headers={"X-Sentry-Data-Source": source},
-        )
-
-    def post(self, request: Request, project: Project) -> Response:
-        """Set remote configuration in project options."""
-        if not features.has(
-            "organizations:remote-config", project.organization, actor=request.user
-        ):
-            return Response("Disabled", status=404)
-
-        validator = ConfigurationContainerValidator(data=request.data)
-        if not validator.is_valid():
-            return self.respond(validator.errors, status=400)
-
-        result = validator.validated_data["data"]
-
-        make_api_backend(project).set(result)
-        metrics.incr("remote_config.configuration.write")
-        return Response({"data": result}, status=201)
-
-    def delete(self, request: Request, project: Project) -> Response:
-        """Delete remote configuration from project options."""
-        if not features.has(
-            "organizations:remote-config", project.organization, actor=request.user
-        ):
-            return Response("Disabled", status=404)
-
-        make_api_backend(project).pop()
-        metrics.incr("remote_config.configuration.delete")
-        return Response("", status=204)
-
-
-@AuthenticationSiloLimit(SiloMode.REGION)
-class RelayAuthentication(BasicAuthentication):
-    """Same as default Relay authentication except without body signing."""
-
-    def authenticate(self, request: Request):
-        return (AnonymousUser(), None)
-
-
-class RemoteConfigRelayPermission(RelayPermission):
-    def has_permission(self, request: Request, view: object) -> bool:
-        # Relay has permission to do everything! Except the only thing we expose is a simple
-        # read endpoint full of public data...
-        return True
-
-
-@region_silo_endpoint
-class ProjectConfigurationProxyEndpoint(Endpoint):
-    publish_status = {
-        "GET": ApiPublishStatus.EXPERIMENTAL,
-    }
-    owner = ApiOwner.REMOTE_CONFIG
-    authentication_classes = (RelayAuthentication,)
-    permission_classes = (RemoteConfigRelayPermission,)
-    enforce_rate_limit = False
-
-    def get(self, request: Request, project_id: int) -> Response:
-        metrics.incr("remote_config.configuration.requested")
-
-        project = Project.objects.select_related("organization").get(pk=project_id)
-        if not features.has("organizations:remote-config", project.organization, actor=None):
-            metrics.incr("remote_config.configuration.flag_disabled")
-            return Response("Disabled", status=404)
-
-        result, source = make_configuration_backend(project).get()
-        if result is None:
-            metrics.incr("remote_config.configuration.not_found")
-            return Response("Not found", status=404)
-
-        result_str = json.dumps(result)
-        metrics.incr("remote_config.configuration.returned")
-        metrics.distribution("remote_config.configuration.size", value=len(result_str))
-
-        # Emulating cache headers just because.
-        return Response(
-            result,
-            status=200,
-            headers={
-                "Cache-Control": "public, max-age=3600",
-                "ETag": hashlib.sha1(result_str.encode()).hexdigest(),
-                "X-Sentry-Data-Source": source,
-            },
-        )

+ 0 - 162
src/sentry/remote_config/storage.py

@@ -1,162 +0,0 @@
-from io import BytesIO
-from typing import TypedDict
-
-from sentry import options
-from sentry.cache import default_cache
-from sentry.models.files.utils import get_storage
-from sentry.models.project import Project
-from sentry.utils import json, metrics
-
-JSONValue = str | int | float | bool | None | list["JSONValue"] | dict[str, "JSONValue"]
-
-
-class Options(TypedDict):
-    sample_rate: float
-    traces_sample_rate: float
-
-
-class Feature(TypedDict):
-    key: str
-    value: JSONValue
-
-
-class StorageFormat(TypedDict):
-    features: list[Feature]
-    options: Options
-    version: int
-
-
-class APIFormat(TypedDict):
-    features: list[Feature]
-    options: Options
-
-
-class ConfigurationCache:
-    def __init__(self, key: str) -> None:
-        self.key = key
-
-    def get(self) -> StorageFormat | None:
-        cache_result = default_cache.get(self.key)
-
-        if cache_result is None:
-            metrics.incr("remote_config.configuration.cache_miss")
-        else:
-            metrics.incr("remote_config.configuration.cache_hit")
-
-        return cache_result
-
-    def set(self, value: StorageFormat) -> None:
-        default_cache.set(self.key, value=value, timeout=None)
-
-    def pop(self) -> None:
-        try:
-            default_cache.delete(self.key)
-        except Exception:
-            pass
-
-
-class ConfigurationStorage:
-    def __init__(self, key: str) -> None:
-        self.key = key
-
-    @property
-    def storage(self):
-        return get_storage(self._make_storage_config())
-
-    def get(self) -> StorageFormat | None:
-        try:
-            blob = self.storage.open(self.key)
-            result = blob.read()
-            blob.close()
-        except Exception:
-            return None
-
-        if result is None:
-            return None
-        return json.loads(result)
-
-    def set(self, value: StorageFormat) -> None:
-        self.storage.save(self.key, BytesIO(json.dumps(value).encode()))
-
-    def pop(self) -> None:
-        try:
-            self.storage.delete(self.key)
-        except Exception:
-            return None
-
-    def _make_storage_config(self) -> dict | None:
-        backend = options.get("configurations.storage.backend")
-        if backend:
-            return {
-                "backend": backend,
-                "options": options.get("configurations.storage.options"),
-            }
-        else:
-            return None
-
-
-class ConfigurationBackend:
-    def __init__(self, project: Project) -> None:
-        self.project = project
-        self.key = f"configurations/{self.project.id}/production"
-
-        self.cache = ConfigurationCache(self.key)
-        self.storage = ConfigurationStorage(self.key)
-
-    def get(self) -> tuple[StorageFormat | None, str]:
-        cache_result = self.cache.get()
-        if cache_result is not None:
-            return (cache_result, "cache")
-
-        storage_result = self.storage.get()
-        if storage_result:
-            self.cache.set(storage_result)
-
-        return (storage_result, "store")
-
-    def set(self, value: StorageFormat) -> None:
-        self.storage.set(value)
-        self.cache.set(value)
-
-    def pop(self) -> None:
-        self.cache.pop()
-        self.storage.pop()
-
-
-class APIBackendDecorator:
-    def __init__(self, backend: ConfigurationBackend) -> None:
-        self.driver = backend
-
-    def get(self) -> tuple[APIFormat | None, str]:
-        result, source = self.driver.get()
-        return self._deserialize(result), source
-
-    def set(self, value: APIFormat) -> None:
-        self.driver.set(self._serialize(value))
-
-    def pop(self) -> None:
-        self.driver.pop()
-
-    def _deserialize(self, result: StorageFormat | None) -> APIFormat | None:
-        if result is None:
-            return None
-
-        return {
-            "features": result["features"],
-            "options": result["options"],
-        }
-
-    def _serialize(self, result: APIFormat) -> StorageFormat:
-        return {
-            "features": result["features"],
-            "options": result["options"],
-            "version": 1,
-        }
-
-
-def make_configuration_backend(project: Project):
-    return ConfigurationBackend(project)
-
-
-def make_api_backend(project: Project):
-    return APIBackendDecorator(make_configuration_backend(project))

+ 0 - 0
tests/sentry/remote_config/__init__.py


Some files were not shown because too many files changed in this diff