Browse Source

Upload Release Bundles as Artifact Bundles (#53770)

This is feature-flagged so we can roll this out gradually.
Arpad Borsos 1 year ago
parent
commit
6e5d8cfaab

+ 19 - 5
src/sentry/api/endpoints/organization_release_assemble.py

@@ -2,6 +2,7 @@ import jsonschema
 from rest_framework.request import Request
 from rest_framework.response import Response
 
+from sentry import features
 from sentry.api.base import region_silo_endpoint
 from sentry.api.bases.organization import OrganizationReleasesBaseEndpoint
 from sentry.api.exceptions import ResourceDoesNotExist
@@ -57,7 +58,19 @@ class OrganizationReleaseAssembleEndpoint(OrganizationReleasesBaseEndpoint):
         checksum = data.get("checksum", None)
         chunks = data.get("chunks", [])
 
-        state, detail = get_assemble_status(AssembleTask.RELEASE_BUNDLE, organization.id, checksum)
+        upload_as_artifact_bundle = False
+        project_ids = []
+        if features.has("organizations:sourcemaps-upload-release-as-artifact-bundle", organization):
+            upload_as_artifact_bundle = True
+            project_ids = [project.id for project in release.projects.all()]
+
+        assemble_task = (
+            AssembleTask.ARTIFACT_BUNDLE
+            if upload_as_artifact_bundle
+            else AssembleTask.RELEASE_BUNDLE
+        )
+
+        state, detail = get_assemble_status(assemble_task, organization.id, checksum)
         if state == ChunkFileState.OK:
             return Response({"state": state, "detail": None, "missingChunks": []}, status=200)
         elif state is not None:
@@ -69,9 +82,7 @@ class OrganizationReleaseAssembleEndpoint(OrganizationReleasesBaseEndpoint):
         if not chunks:
             return Response({"state": ChunkFileState.NOT_FOUND, "missingChunks": []}, status=200)
 
-        set_assemble_status(
-            AssembleTask.RELEASE_BUNDLE, organization.id, checksum, ChunkFileState.CREATED
-        )
+        set_assemble_status(assemble_task, organization.id, checksum, ChunkFileState.CREATED)
 
         from sentry.tasks.assemble import assemble_artifacts
 
@@ -81,7 +92,10 @@ class OrganizationReleaseAssembleEndpoint(OrganizationReleasesBaseEndpoint):
                 "version": version,
                 "checksum": checksum,
                 "chunks": chunks,
-                "upload_as_artifact_bundle": False,
+                # NOTE: The `dist` is embedded in the Bundle manifest and optional here.
+                # It will be backfilled from the manifest within the `assemble_artifacts` task.
+                "project_ids": project_ids,
+                "upload_as_artifact_bundle": upload_as_artifact_bundle,
             }
         )
 

+ 2 - 0
src/sentry/conf/server.py

@@ -1716,6 +1716,8 @@ SENTRY_FEATURES = {
     "projects:custom-inbound-filters": False,
     # Enable the new flat file indexing system for sourcemaps.
     "organizations:sourcemaps-bundle-flat-file-indexing": False,
+    # Upload release bundles as artifact bundles.
+    "organizations:sourcemaps-upload-release-as-artifact-bundle": False,
     # Signals that the organization supports the on demand metrics prefill.
     "organizations:on-demand-metrics-prefill": False,
     # Signals that the organization can start prefilling on demand metrics.

+ 1 - 0
src/sentry/features/__init__.py

@@ -268,6 +268,7 @@ default_manager.add("organizations:ds-org-recalibration", OrganizationFeature, F
 default_manager.add("organizations:github-disable-on-broken", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
 default_manager.add("organizations:slack-fatal-disable-on-broken", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
 default_manager.add("organizations:sourcemaps-bundle-flat-file-indexing", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
+default_manager.add("organizations:sourcemaps-upload-release-as-artifact-bundle", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
 default_manager.add("organizations:recap-server", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("organizations:detailed-alert-logging", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("organizations:notification-settings-v2", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)

+ 52 - 0
tests/sentry/api/endpoints/test_organization_release_assemble.py

@@ -5,9 +5,11 @@ from django.core.files.base import ContentFile
 from django.urls import reverse
 
 from sentry.models import ApiToken, FileBlob, FileBlobOwner
+from sentry.models.artifactbundle import ArtifactBundle
 from sentry.silo import SiloMode
 from sentry.tasks.assemble import ChunkFileState, assemble_artifacts
 from sentry.testutils.cases import APITestCase
+from sentry.testutils.helpers.features import with_feature
 from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
 
 
@@ -79,6 +81,7 @@ class OrganizationReleaseAssembleTest(APITestCase):
                 "version": self.release.version,
                 "chunks": [blob1.checksum],
                 "checksum": total_checksum,
+                "project_ids": [],
                 "upload_as_artifact_bundle": False,
             }
         )
@@ -128,3 +131,52 @@ class OrganizationReleaseAssembleTest(APITestCase):
 
         assert response.status_code == 200, response.content
         assert response.data["state"] == ChunkFileState.ERROR
+
+    @patch("sentry.tasks.assemble.assemble_artifacts")
+    @with_feature("organizations:sourcemaps-upload-release-as-artifact-bundle")
+    def test_assemble_as_artifact_bundle(self, mock_assemble_artifacts):
+        bundle_file = self.create_artifact_bundle_zip(
+            org=self.organization.slug, release=self.release.version
+        )
+        total_checksum = sha1(bundle_file).hexdigest()
+
+        blob1 = FileBlob.from_file(ContentFile(bundle_file))
+        FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob1)
+
+        response = self.client.post(
+            self.url,
+            data={"checksum": total_checksum, "chunks": [blob1.checksum]},
+            HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
+        )
+
+        assert response.status_code == 200, response.content
+        assert response.data["state"] == ChunkFileState.CREATED
+        assert set(response.data["missingChunks"]) == set()
+
+        # assert that we are uploading as artifact bundle
+        kwargs = {
+            "org_id": self.organization.id,
+            "version": self.release.version,
+            "checksum": total_checksum,
+            "chunks": [blob1.checksum],
+            "project_ids": [self.project.id],
+            "upload_as_artifact_bundle": True,
+        }
+        mock_assemble_artifacts.apply_async.assert_called_once_with(kwargs=kwargs)
+        # actually call through to assemble :-)
+        assemble_artifacts(**kwargs)
+
+        response = self.client.post(
+            self.url,
+            data={"checksum": total_checksum, "chunks": [blob1.checksum]},
+            HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
+        )
+
+        assert response.status_code == 200, response.content
+        assert response.data["state"] == ChunkFileState.OK
+
+        # make sure that we have an artifact bundle now
+        artifact_bundles = ArtifactBundle.objects.filter(
+            organization_id=self.organization.id,
+        )
+        assert len(artifact_bundles) == 1