Skip to content

Conversation

@jasonyuezhang
Copy link
Owner

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.


Copied from getsentry#102117
Original PR: getsentry#102117

@propel-test-bot propel-test-bot bot changed the title Add app-icon download endpoint Add Preprod API endpoint for downloading app icons Oct 25, 2025
@propel-test-bot
Copy link

propel-test-bot bot commented Oct 25, 2025

Add API endpoint for downloading pre-prod app icons

Introduces a new public (experimental) endpoint ProjectPreprodArtifactIconEndpoint that allows clients to fetch stored application icons tied to pre-prod artifacts. The handler streams binary data from the new object-store namespace app_icons, performs magic-byte inspection to derive the correct MIME type, and returns the icon as an HttpResponse. Ancillary changes expose the route in preprod_urlpatterns, extend the build-details pydantic model with an app_icon_id field, and register a dedicated client builder in sentry.objectstore.__init__.

No existing code is removed; all changes are additive.

Key Changes

• Added endpoint implementation src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py with helper detect_image_content_type() and full logging / error handling
• Registered new route files/app-icons/<app_icon_id>/ in src/sentry/preprod/api/endpoints/urls.py mapped to the new endpoint
• Extended data model BuildDetailsAppInfo with new attribute app_icon_id and populated it in transform_preprod_artifact_to_build_details()
• Created new object-store client app_icons = ClientBuilder("app-icons") in src/sentry/objectstore/__init__.py

Affected Areas

preprod API endpoints
• URL routing for preprod endpoints
• Preprod build-details pydantic models
• Object-store client configuration

This summary was automatically generated by @propel-code-bot

)

# Upload failed, return appropriate error
return HttpResponse({"error": "Not found"}, status=404)

Choose a reason for hiding this comment

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

[CriticalError]

The HttpResponse constructor expects either a string/bytes as the first argument or uses keyword arguments for structured data. Line 117 and 129 are passing dictionaries directly as the first argument, which will result in the dictionary being converted to a string representation like "{'error': 'Not found'}" instead of proper JSON.

Suggested change
return HttpResponse({"error": "Not found"}, status=404)
return HttpResponse(json.dumps({"error": "Not found"}), content_type="application/json", status=404)

Committable suggestion

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Context for Agents
[**CriticalError**]

The `HttpResponse` constructor expects either a string/bytes as the first argument or uses keyword arguments for structured data. Line 117 and 129 are passing dictionaries directly as the first argument, which will result in the dictionary being converted to a string representation like `"{'error': 'Not found'}"` instead of proper JSON.

```suggestion
return HttpResponse(json.dumps({"error": "Not found"}), content_type="application/json", status=404)
```

⚡ **Committable suggestion**

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

File: src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py
Line: 117

"status": e.status,
},
)
return HttpResponse({"error": "Failed to retrieve app icon"}, status=500)

Choose a reason for hiding this comment

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

[BestPractice]

Same issue here - the dictionary will be converted to string representation instead of proper JSON. This needs to import json module and serialize the dictionary.

Suggested change
return HttpResponse({"error": "Failed to retrieve app icon"}, status=500)
return HttpResponse(json.dumps({"error": "Failed to retrieve app icon"}), content_type="application/json", status=500)

Committable suggestion

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Context for Agents
[**BestPractice**]

Same issue here - the dictionary will be converted to string representation instead of proper JSON. This needs to import `json` module and serialize the dictionary.

```suggestion
return HttpResponse(json.dumps({"error": "Failed to retrieve app icon"}), content_type="application/json", status=500)
```

⚡ **Committable suggestion**

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

File: src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py
Line: 129

"app_icon_id": app_icon_id,
},
)
return HttpResponse({"error": "Internal server error"}, status=500)

Choose a reason for hiding this comment

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

[BestPractice]

Same issue here - the dictionary needs to be JSON serialized.

Suggested change
return HttpResponse({"error": "Internal server error"}, status=500)
return HttpResponse(json.dumps({"error": "Internal server error"}), content_type="application/json", status=500)

Committable suggestion

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Context for Agents
[**BestPractice**]

Same issue here - the dictionary needs to be JSON serialized.

```suggestion
return HttpResponse(json.dumps({"error": "Internal server error"}), content_type="application/json", status=500)
```

⚡ **Committable suggestion**

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

File: src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py
Line: 140

Comment on lines +86 to +88
result = client.get(app_icon_id)
# Read the entire stream at once
image_data = result.payload.read()

Choose a reason for hiding this comment

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

[BestPractice]

Resource leak: The result.payload.read() call reads from a stream but there's no guarantee that the underlying stream/connection is properly closed. If result.payload implements a context manager or has a close method, it should be used to ensure proper resource cleanup.

Consider using a context manager pattern:

with client.get(app_icon_id) as result:
    image_data = result.payload.read()

Or explicitly close the resource:

result = client.get(app_icon_id)
try:
    image_data = result.payload.read()
finally:
    if hasattr(result.payload, 'close'):
        result.payload.close()
Context for Agents
[**BestPractice**]

Resource leak: The `result.payload.read()` call reads from a stream but there's no guarantee that the underlying stream/connection is properly closed. If `result.payload` implements a context manager or has a close method, it should be used to ensure proper resource cleanup.

Consider using a context manager pattern:
```python
with client.get(app_icon_id) as result:
    image_data = result.payload.read()
```

Or explicitly close the resource:
```python
result = client.get(app_icon_id)
try:
    image_data = result.payload.read()
finally:
    if hasattr(result.payload, 'close'):
        result.payload.close()
```

File: src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py
Line: 88

},
)

# Upload failed, return appropriate error

Choose a reason for hiding this comment

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

[Documentation]

The comment says "Upload failed, return appropriate error" but this is actually a GET endpoint for retrieving icons, not uploading. The comment should be corrected to reflect the actual operation.

Suggested change
# Upload failed, return appropriate error
# App icon not found, return appropriate error

Committable suggestion

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Context for Agents
[**Documentation**]

The comment says "Upload failed, return appropriate error" but this is actually a GET endpoint for retrieving icons, not uploading. The comment should be corrected to reflect the actual operation.

```suggestion
                # App icon not found, return appropriate error
```

⚡ **Committable suggestion**

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

File: src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py
Line: 116


import logging

from django.http import HttpResponse

Choose a reason for hiding this comment

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

[CriticalError]

Incorrect return type for dictionary response: Django's HttpResponse expects bytes or string content, but you're passing a dictionary {"error": "Not found"}. This will cause a TypeError at runtime. Use JsonResponse instead:

Suggested change
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse

Committable suggestion

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Context for Agents
[**CriticalError**]

Incorrect return type for dictionary response: Django's `HttpResponse` expects bytes or string content, but you're passing a dictionary `{"error": "Not found"}`. This will cause a `TypeError` at runtime. Use `JsonResponse` instead:

```suggestion
from django.http import HttpResponse, JsonResponse
```

⚡ **Committable suggestion**

Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

File: src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py
Line: 5

@jasonyuezhang jasonyuezhang force-pushed the ryan/add_api_to_serve_preprod_app_icons branch from dec7590 to 1027469 Compare October 26, 2025 05:30
@propel-test-bot propel-test-bot bot changed the title Add Preprod API endpoint for downloading app icons Add Preprod endpoint to download project app icons Oct 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants