Skip to content

feat(launchpad): attempt to wire up kafka analysis logic #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 1 addition & 2 deletions .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export LAUNCHPAD_CREATE_KAFKA_TOPIC="1"
export LAUNCHPAD_ENV="development"
export LAUNCHPAD_HOST="0.0.0.0"
export LAUNCHPAD_PORT="2218"
export LAUNCHPAD_RPC_SHARED_SECRET="launchpad-also-very-long-value-haha"
# STATSD_HOST=... # defaults to 127.0.0.1
# STATSD_PORT=... # defaults to 8125

Expand Down Expand Up @@ -44,5 +45,3 @@ fi

export VIRTUAL_ENV="${PWD}/.venv"
PATH_add "${PWD}/.venv/bin"


9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ test-kafka-message: ## Send a test message to Kafka (requires Kafka running)
test-kafka-multiple: ## Send multiple test messages to Kafka
$(PYTHON_VENV) scripts/test_kafka.py --count 5 --interval 0

test-download-artifact:
$(PYTHON_VENV) scripts/test_download_artifact.py --verbose

test-artifact-update:
$(PYTHON_VENV) scripts/test_artifact_update.py --build-version "1.0.0" --build-number 42 --verbose

test-artifact-size-analysis-upload:
$(PYTHON_VENV) scripts/test_artifact_size_analysis_upload.py --verbose

test-service-integration: ## Run full integration test with devservices
@echo "Starting Kafka services via devservices..."
@devservices up
Expand Down
96 changes: 96 additions & 0 deletions scripts/test_artifact_size_analysis_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Script to test uploading size analysis files to Sentry using the complete chunking flow."""

import json
import logging
import os
import sys

import click

sys.path.insert(0, "src")
from launchpad.sentry_client import SentryClient


@click.command()
@click.option(
"--base-url", default="http://localhost:8000", help="Base URL for Sentry API"
)
@click.option("--org", default="sentry", help="Organization slug")
@click.option("--project", default="internal", help="Project slug")
@click.option("--artifact-id", default="1", help="Artifact ID to upload analysis for")
@click.option("--file-path", default="README.md", help="Path to file to upload")
@click.option("--verbose", is_flag=True, help="Enable verbose logging")
def main(
base_url: str,
org: str,
project: str,
artifact_id: str,
file_path: str,
verbose: bool,
) -> None:
"""Test uploading size analysis files to Sentry using the complete chunking flow."""

logging.basicConfig(
level=logging.DEBUG if verbose else logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)

# Verify file exists
if not os.path.exists(file_path):
click.echo(f"❌ File not found: {file_path}")
sys.exit(1)

file_size = os.path.getsize(file_path)

try:
click.echo(
f"Testing size analysis upload: {org}/{project}/artifacts/{artifact_id}"
)
click.echo(f"📁 File: {file_path} ({file_size:,} bytes)")

client = SentryClient(base_url=base_url)

click.echo("🔄 Starting size analysis upload...")
click.echo(" 📊 Calculating checksums...")
click.echo(" 🚀 Calling assemble endpoint...")

response = client.upload_size_analysis_file(
org, project, artifact_id, file_path
)

if "error" in response:
click.echo(
f"❌ Failed: {response['error']} (Status: {response.get('status_code', 'Unknown')})"
)
if "message" in response:
click.echo(f" Message: {response['message']}")
sys.exit(1)

click.echo("✅ Size analysis upload successful!")

# Show relevant response data
if "checksum" in response:
click.echo(f"📄 File checksum: {response['checksum']}")
if "chunks" in response:
click.echo(f"🧩 Chunks processed: {len(response['chunks'])} chunk(s)")
if "state" in response:
click.echo(f"📊 Analysis state: {response['state']}")

if verbose:
click.echo(f"📄 Full response: {json.dumps(response, indent=2)}")

except FileNotFoundError as e:
click.echo(f"❌ File error: {e}")
sys.exit(1)
except Exception as e:
click.echo(f"❌ Error: {e}")
if verbose:
import traceback

click.echo(traceback.format_exc())
sys.exit(1)


if __name__ == "__main__":
main()
104 changes: 104 additions & 0 deletions scripts/test_artifact_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Script to test updating artifacts in Sentry using the internal endpoint."""

import json
import logging
import sys
from datetime import datetime

import click

sys.path.insert(0, "src")
from launchpad.sentry_client import SentryClient


@click.command()
@click.option(
"--base-url", default="http://localhost:8000", help="Base URL for Sentry API"
)
@click.option("--org", default="sentry", help="Organization slug")
@click.option("--project", default="internal", help="Project slug")
@click.option("--artifact-id", default="1", help="Artifact ID to update")
@click.option("--artifact-type", type=int, help="Artifact type (0=APK, 1=IPA, 2=AAB)")
@click.option("--build-version", help="Build version string")
@click.option("--build-number", type=int, help="Build number")
@click.option("--error-message", help="Error message (sets state to FAILED)")
@click.option("--verbose", is_flag=True, help="Enable verbose logging")
def main(
base_url: str,
org: str,
project: str,
artifact_id: str,
artifact_type: int | None,
build_version: str | None,
build_number: int | None,
error_message: str | None,
verbose: bool,
) -> None:
"""Test updating artifacts in Sentry using the internal endpoint."""

logging.basicConfig(
level=logging.DEBUG if verbose else logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)

# Build update data from provided options
update_data = {}

if artifact_type is not None:
update_data["artifact_type"] = artifact_type
if build_version:
update_data["build_version"] = build_version
if build_number is not None:
update_data["build_number"] = build_number
if error_message:
update_data["error_message"] = error_message

# Add a timestamp to show it was updated
update_data["date_built"] = datetime.utcnow().isoformat() + "Z"

if not update_data or update_data == {"date_built": update_data["date_built"]}:
click.echo("❌ No update fields provided. Use --help to see available options.")
sys.exit(1)

try:
click.echo(f"Testing update: {org}/{project}/artifacts/{artifact_id}")
click.echo(f"Update data: {json.dumps(update_data, indent=2)}")

client = SentryClient(base_url=base_url)
response = client.update_artifact(org, project, artifact_id, update_data)

if "error" in response:
click.echo(
f"❌ Failed: {response['error']} (Status: {response.get('status_code', 'Unknown')})"
)
if "message" in response:
click.echo(f" Message: {response['message']}")
sys.exit(1)

success = response.get("success", False)
updated_fields = response.get("updated_fields", [])
artifact_id_resp = response.get("artifact_id", "Unknown")

if success:
click.echo(f"✅ Successfully updated artifact {artifact_id_resp}")
click.echo(
f"📝 Updated fields: {', '.join(updated_fields) if updated_fields else 'none'}"
)
else:
click.echo("⚠️ Update completed but success flag is False")

if verbose:
click.echo(f"📄 Full response: {json.dumps(response, indent=2)}")

except Exception as e:
click.echo(f"❌ Error: {e}")
if verbose:
import traceback

click.echo(traceback.format_exc())
sys.exit(1)


if __name__ == "__main__":
main()
92 changes: 92 additions & 0 deletions scripts/test_download_artifact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Script to test downloading artifacts from Sentry using the internal endpoint."""

import json
import logging
import os
import sys
import time

import click

sys.path.insert(0, "src")
from launchpad.sentry_client import SentryClient


@click.command()
@click.option(
"--base-url", default="http://localhost:8000", help="Base URL for Sentry API"
)
@click.option("--org", default="sentry", help="Organization slug")
@click.option("--project", default="internal", help="Project slug")
@click.option("--artifact-id", default="1", help="Artifact ID to download")
@click.option("--verbose", is_flag=True, help="Enable verbose logging")
def main(
base_url: str, org: str, project: str, artifact_id: str, verbose: bool
) -> None:
"""Test downloading artifacts from Sentry using the internal endpoint."""

logging.basicConfig(
level=logging.DEBUG if verbose else logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)

try:
click.echo(f"Testing download: {org}/{project}/artifacts/{artifact_id}")

client = SentryClient(base_url=base_url)
start_time = time.time()

response = client.download_artifact(org, project, artifact_id)
duration = time.time() - start_time

if "error" in response:
click.echo(
f"❌ Failed: {response['error']} (Status: {response.get('status_code', 'Unknown')})"
)
sys.exit(1)

file_content = response.get("file_content", b"")
file_size = len(file_content)

if not file_content:
click.echo("❌ No file content received")
sys.exit(1)

# Save file to disk
timestamp = int(time.time())
file_ext = ".zip" if file_content.startswith(b"PK") else ".bin"
filename = (
f"preprod_artifact_{org}_{project}_{artifact_id}_{timestamp}{file_ext}"
)
file_path = os.path.join(os.getcwd(), filename)

with open(file_path, "wb") as f:
f.write(file_content)

# Verify and report
disk_size = os.path.getsize(file_path)
integrity_ok = file_size == disk_size

click.echo(f"✅ Downloaded {file_size:,} bytes in {duration:.2f}s")
click.echo(f"💾 Saved to: {file_path}")
click.echo(
f"{'✅' if integrity_ok else '⚠️ '} File integrity: {'OK' if integrity_ok else 'MISMATCH'}"
)

if verbose:
click.echo(
f"📄 Headers: {json.dumps(response.get('headers', {}), indent=2)}"
)

except Exception as e:
click.echo(f"❌ Error: {e}")
if verbose:
import traceback

click.echo(traceback.format_exc())
sys.exit(1)


if __name__ == "__main__":
main()
Loading