-
-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Extract and send tooling versions to Sentry (EME-606) #464
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
Changes from all commits
f12c060
47a4a6a
5998900
4b10d48
0cfd711
8bb5f86
0a59a68
acb6caa
3b2c2d5
76cdd8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -442,12 +442,16 @@ def _get_artifact_type(artifact: Artifact) -> ArtifactType: | |
| certificate_expiration_date=app_info.certificate_expiration_date, | ||
| missing_dsym_binaries=app_info.missing_dsym_binaries, | ||
| build_date=app_info.build_date, | ||
| cli_version=app_info.cli_version, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say keep here so that the apple_app_info/android_app_info keep parity with our backend models |
||
| fastlane_plugin_version=app_info.fastlane_plugin_version, | ||
| ) | ||
|
|
||
| android_app_info = None | ||
| if isinstance(app_info, AndroidAppInfo): | ||
| android_app_info = AndroidAppInfoModel( | ||
| has_proguard_mapping=app_info.has_proguard_mapping, | ||
| cli_version=app_info.cli_version, | ||
| gradle_plugin_version=app_info.gradle_plugin_version, | ||
| ) | ||
|
|
||
| update_data = UpdateData( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import zipfile | ||
|
|
||
| from pathlib import Path | ||
| from typing import Dict, Optional | ||
|
|
||
| from launchpad.utils.logging import get_logger | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
||
| METADATA_FILENAME = ".sentry-cli-metadata.txt" | ||
|
|
||
|
|
||
| class ToolingMetadata: | ||
| def __init__( | ||
| self, | ||
| cli_version: Optional[str] = None, | ||
| fastlane_plugin_version: Optional[str] = None, | ||
| gradle_plugin_version: Optional[str] = None, | ||
| ): | ||
| self.cli_version = cli_version | ||
| self.fastlane_plugin_version = fastlane_plugin_version | ||
| self.gradle_plugin_version = gradle_plugin_version | ||
|
|
||
|
|
||
| def extract_metadata_from_zip(zip_path: Path) -> ToolingMetadata: | ||
| try: | ||
| with zipfile.ZipFile(zip_path, "r") as zf: | ||
| # Only look for .sentry-cli-metadata.txt in the root of the zip | ||
| if METADATA_FILENAME not in zf.namelist(): | ||
| logger.debug(f"No {METADATA_FILENAME} found in root of {zip_path}") | ||
| return ToolingMetadata() | ||
|
|
||
| logger.debug(f"Found metadata file: {METADATA_FILENAME}") | ||
|
|
||
| with zf.open(METADATA_FILENAME) as f: | ||
| content = f.read().decode("utf-8") | ||
| return _parse_metadata_content(content) | ||
|
|
||
| except Exception as e: | ||
| logger.warning(f"Failed to extract metadata from {zip_path}: {e}") | ||
| return ToolingMetadata() | ||
|
|
||
|
|
||
| def _parse_metadata_content(content: str) -> ToolingMetadata: | ||
| """Expected format: | ||
| sentry-cli-version: 2.58.2 | ||
| sentry-fastlane-plugin: 1.2.3 | ||
| sentry-gradle-plugin: 4.12.0 | ||
| """ | ||
| metadata: Dict[str, str] = {} | ||
|
|
||
| for line in content.strip().split("\n"): | ||
| line = line.strip() | ||
| if not line or ":" not in line: | ||
| continue | ||
|
|
||
| key, value = line.split(":", 1) | ||
| key = key.strip() | ||
| value = value.strip() | ||
|
|
||
| metadata[key] = value | ||
|
|
||
| return ToolingMetadata( | ||
| cli_version=metadata.get("sentry-cli-version"), | ||
| fastlane_plugin_version=metadata.get("sentry-fastlane-plugin"), | ||
| gradle_plugin_version=metadata.get("sentry-gradle-plugin"), | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import tempfile | ||
| import zipfile | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| from launchpad.utils.metadata_extractor import ( | ||
| ToolingMetadata, | ||
| _parse_metadata_content, | ||
| extract_metadata_from_zip, | ||
| ) | ||
|
|
||
|
|
||
| class TestParseMetadataContent: | ||
| def test_parse_all_fields(self): | ||
| content = """sentry-cli-version: 2.58.2 | ||
| sentry-fastlane-plugin: 1.2.3 | ||
| sentry-gradle-plugin: 4.12.0""" | ||
| metadata = _parse_metadata_content(content) | ||
| assert metadata.cli_version == "2.58.2" | ||
| assert metadata.fastlane_plugin_version == "1.2.3" | ||
| assert metadata.gradle_plugin_version == "4.12.0" | ||
|
|
||
| def test_parse_empty_content(self): | ||
| content = "" | ||
| metadata = _parse_metadata_content(content) | ||
| assert metadata.cli_version is None | ||
| assert metadata.fastlane_plugin_version is None | ||
| assert metadata.gradle_plugin_version is None | ||
|
|
||
|
|
||
| class TestExtractMetadataFromZip: | ||
| def test_extract_from_zip_root(self): | ||
| with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tf: | ||
| try: | ||
| with zipfile.ZipFile(tf.name, "w") as zf: | ||
| zf.writestr( | ||
| ".sentry-cli-metadata.txt", | ||
| "sentry-cli-version: 2.58.2\nsentry-fastlane-plugin: 1.2.3\nsentry-gradle-plugin: 4.12.0", | ||
| ) | ||
| zf.writestr("some-file.txt", "content") | ||
|
|
||
| metadata = extract_metadata_from_zip(Path(tf.name)) | ||
| assert metadata.cli_version == "2.58.2" | ||
| assert metadata.fastlane_plugin_version == "1.2.3" | ||
| assert metadata.gradle_plugin_version == "4.12.0" | ||
| finally: | ||
| Path(tf.name).unlink() | ||
|
|
||
| def test_extract_when_missing(self): | ||
| with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tf: | ||
| try: | ||
| with zipfile.ZipFile(tf.name, "w") as zf: | ||
| zf.writestr("some-file.txt", "content") | ||
| zf.writestr("other-file.txt", "content") | ||
|
|
||
| metadata = extract_metadata_from_zip(Path(tf.name)) | ||
| assert metadata.cli_version is None | ||
| assert metadata.fastlane_plugin_version is None | ||
| assert metadata.gradle_plugin_version is None | ||
| finally: | ||
| Path(tf.name).unlink() | ||
|
|
||
|
|
||
| class TestToolingMetadata: | ||
| def test_create_with_defaults(self): | ||
| metadata = ToolingMetadata() | ||
| assert metadata.cli_version is None | ||
| assert metadata.fastlane_plugin_version is None | ||
| assert metadata.gradle_plugin_version is None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this base class to reduce duplication of the
cli_version. Can remove if it seems redundant.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, perfect use case of this base pattern 👍