Skip to content
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
27 changes: 23 additions & 4 deletions src/sentry/api/endpoints/chunk.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import re
from gzip import GzipFile
from io import BytesIO
from urllib.parse import urljoin
Expand All @@ -18,6 +19,7 @@
MAX_REQUEST_SIZE = 32 * 1024 * 1024
MAX_CONCURRENCY = settings.DEBUG and 1 or 8
HASH_ALGORITHM = "sha1"
SENTRYCLI_SEMVER_RE = re.compile(r"^sentry-cli\/(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).*$")

CHUNK_UPLOAD_ACCEPT = (
"debug_files", # DIF assemble
Expand Down Expand Up @@ -48,12 +50,29 @@ def get(self, request, organization):
endpoint = options.get("system.upload-url-prefix")
relative_url = reverse("sentry-api-0-chunk-upload", args=[organization.slug])

# Starting `sentry-cli@1.70.1` we added a support for relative chunk-uploads urls
# User-Agent: sentry-cli/1.70.1
user_agent = request.headers.get("User-Agent", "")
sentrycli_version = SENTRYCLI_SEMVER_RE.search(user_agent)
supports_relative_url = (
(sentrycli_version is not None)
and (int(sentrycli_version.group("major")) >= 1)
and (int(sentrycli_version.group("minor")) >= 70)
and (int(sentrycli_version.group("patch")) >= 1)
Comment on lines +59 to +61
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: to me this looks more pythonic as (v.group('major'), v.group('minor'), v.group('patch')) >= (1, 70, 1), but i think it's functionally equivalent so doesn't really matter

Copy link
Contributor Author

@kamilogorek kamilogorek Oct 21, 2021

Choose a reason for hiding this comment

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

It ends up being formatted as

supports_relative_url = (sentrycli_version is not None) and (
    (
        int(sentrycli_version.group("major")),
        int(sentrycli_version.group("minor")),
        int(sentrycli_version.group("patch")),
    )
    >= (1, 70, 1)
)

which is kinda ugly. Even with shorter name its still "meh", especially that we need to cast it.

)

# If user do not overwritten upload url prefix
if len(endpoint) == 0:
# If config is not set, we return relative, versionless endpoint (with `/api/0` stripped)
prefix = "/api/0"
url = relative_url.replace(prefix, "/")
# And we support relative url uploads, return a relative, versionless endpoint (with `/api/0` stripped)
if supports_relative_url:
prefix = "/api/0"
url = relative_url.replace(prefix, "/")
# Otherwise, if we do not support them, return an absolute, versioned endpoint with a default, system-wide prefix
else:
endpoint = options.get("system.url-prefix")
url = urljoin(endpoint.rstrip("/") + "/", relative_url.lstrip("/"))
else:
# Otherwise, we want a relative, versioned endpoint, with user-configured prefix
# If user overriden upload url prefix, we want an absolute, versioned endpoint, with user-configured prefix
url = urljoin(endpoint.rstrip("/") + "/", relative_url.lstrip("/"))

return Response(
Expand Down
54 changes: 53 additions & 1 deletion tests/sentry/api/endpoints/test_chunk_upload.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from hashlib import sha1

import pytest
from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
Expand All @@ -16,6 +17,10 @@


class ChunkUploadTest(APITestCase):
@pytest.fixture(autouse=True)
def _restore_upload_url_options(self):
options.delete("system.upload-url-prefix")

def setUp(self):
self.organization = self.create_organization(owner=self.user)
self.token = ApiToken.objects.create(user=self.user, scope_list=["project:write"])
Expand All @@ -33,7 +38,7 @@ def test_chunk_parameters(self):
assert response.data["maxFileSize"] == options.get("system.maximum-file-size")
assert response.data["concurrency"] == MAX_CONCURRENCY
assert response.data["hashAlgorithm"] == HASH_ALGORITHM
assert response.data["url"] == self.url.replace("/api/0", "/")
assert response.data["url"] == options.get("system.url-prefix") + self.url

options.set("system.upload-url-prefix", "test")
response = self.client.get(
Expand All @@ -42,6 +47,53 @@ def test_chunk_parameters(self):

assert response.data["url"] == options.get("system.upload-url-prefix") + self.url

def test_relative_url_support(self):
# Starting `sentry-cli@1.70.1` we added a support for relative chunk-uploads urls

# >= 1.70.1
response = self.client.get(
self.url,
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
HTTP_USER_AGENT="sentry-cli/1.70.1",
format="json",
)
assert response.data["url"] == self.url.replace("/api/0", "/")

response = self.client.get(
self.url,
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
HTTP_USER_AGENT="sentry-cli/2.77.4",
format="json",
)
assert response.data["url"] == self.url.replace("/api/0", "/")

# < 1.70.1
response = self.client.get(
self.url,
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
HTTP_USER_AGENT="sentry-cli/1.70.0",
format="json",
)
assert response.data["url"] == options.get("system.url-prefix") + self.url

response = self.client.get(
self.url,
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
HTTP_USER_AGENT="sentry-cli/0.69.3",
format="json",
)
assert response.data["url"] == options.get("system.url-prefix") + self.url

# user overriden upload url prefix has priority, even when calling from sentry-cli that supports relative urls
options.set("system.upload-url-prefix", "test")
response = self.client.get(
self.url,
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
HTTP_USER_AGENT="sentry-cli/1.70.1",
format="json",
)
assert response.data["url"] == options.get("system.upload-url-prefix") + self.url

def test_large_uploads(self):
with self.feature("organizations:large-debug-files"):
response = self.client.get(
Expand Down