Skip to content
14 changes: 11 additions & 3 deletions src/launchpad/artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from launchpad.artifacts.artifact_factory import ArtifactFactory
from launchpad.constants import (
ArtifactType,
InstallableAppErrorCode,
PreprodFeature,
ProcessingErrorCode,
ProcessingErrorMessage,
Expand Down Expand Up @@ -331,9 +332,16 @@ def _do_distribution(
with apk.raw_file() as f:
self._sentry_client.upload_installable_app(organization_id, project_id, artifact_id, f)
else:
# TODO(EME-422): Should call _update_artifact_error here once we
# support setting errors just for build.
logger.error(f"BUILD_DISTRIBUTION failed for {artifact_id} (project: {project_id}, org: {organization_id})")
logger.error(f"BUILD_DISTRIBUTION failed for {artifact_id}: unsupported artifact type")
try:
self._sentry_client.update_distribution_error(
org=organization_id,
artifact_id=artifact_id,
error_code=InstallableAppErrorCode.PROCESSING_ERROR.value,
error_message="unsupported artifact type",
)
except Exception:
logger.exception(f"Failed to update distribution error for artifact {artifact_id}")

def _do_size(
self,
Expand Down
8 changes: 8 additions & 0 deletions src/launchpad/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ class PreprodFeature(Enum):
BUILD_DISTRIBUTION = "build_distribution"


# Matches InstallableApp.ErrorCode in sentry
class InstallableAppErrorCode(Enum):
UNKNOWN = 0
NO_QUOTA = 1
SKIPPED = 2
PROCESSING_ERROR = 3


# Health check threshold - consider unhealthy if file not touched in 60 seconds
HEALTHCHECK_MAX_AGE_SECONDS = 60.0

Expand Down
18 changes: 18 additions & 0 deletions src/launchpad/sentry_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,24 @@ def update_artifact(self, org: str, project: str, artifact_id: str, data: Dict[s
endpoint = f"/api/0/internal/{org}/{project}/files/preprodartifacts/{artifact_id}/update/"
return self._make_json_request("PUT", endpoint, UpdateResponse, data=data)

def update_distribution_error(self, org: str, artifact_id: str, error_code: int, error_message: str) -> None:
"""Report distribution error via the dedicated distribution endpoint."""
endpoint = f"/api/0/organizations/{org}/preprodartifacts/{artifact_id}/distribution/"
url = self._build_url(endpoint)
body = json.dumps({"error_code": error_code, "error_message": error_message}).encode("utf-8")

logger.debug(f"PUT {url}")
response = self.session.request(
method="PUT",
url=url,
data=body,
auth=self.auth,
timeout=30,
)

if response.status_code != 200:
raise SentryClientError(response=response)

def upload_size_analysis_file(
self,
org: str,
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/artifacts/test_artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
)

from launchpad.artifact_processor import ArtifactProcessor
from launchpad.artifacts.artifact import Artifact
from launchpad.constants import (
InstallableAppErrorCode,
ProcessingErrorCode,
ProcessingErrorMessage,
)
Expand Down Expand Up @@ -137,6 +139,25 @@ def test_processing_error_message_enum_values(self):
assert ProcessingErrorMessage.SIZE_ANALYSIS_FAILED.value == "Failed to perform size analysis"
assert ProcessingErrorMessage.UNKNOWN_ERROR.value == "An unknown error occurred"

def test_do_distribution_unknown_artifact_type_reports_error(self):
mock_sentry_client = Mock(spec=SentryClient)
mock_sentry_client.update_distribution_error.return_value = None
self.processor._sentry_client = mock_sentry_client

unknown_artifact = Mock(spec=Artifact)
mock_info = Mock()

self.processor._do_distribution(
"test-org-id", "test-project-id", "test-artifact-id", unknown_artifact, mock_info
)

mock_sentry_client.update_distribution_error.assert_called_once_with(
org="test-org-id",
artifact_id="test-artifact-id",
error_code=InstallableAppErrorCode.PROCESSING_ERROR.value,
error_message="unsupported artifact type",
)


class TestArtifactProcessorMessageHandling:
"""Test message processing functionality in ArtifactProcessor."""
Expand Down
Loading