diff --git a/nanolayer/cli/__init__.py b/nanolayer/cli/__init__.py index e69de29b..234303a1 100644 --- a/nanolayer/cli/__init__.py +++ b/nanolayer/cli/__init__.py @@ -0,0 +1,56 @@ +import logging +import os +import platform + +import psutil +import sentry_sdk + +from nanolayer.utils.linux_information_desk import EnvFile +from nanolayer.utils.settings import NanolayerSettings +from nanolayer.utils.version import resolve_own_package_version + +logger = logging.getLogger(__name__) + +try: + sentry_sdk.init( + release=resolve_own_package_version(), + traces_sample_rate=1.0, + dsn=NanolayerSettings().analytics_id + if not NanolayerSettings().no_analytics + else "", + # explicitly turn off any feature which have an + # impact on personally identifiable information + send_default_pii=False, + send_client_reports=False, + request_bodies="never", + ) + # explicitly strip any identifiable user information + sentry_sdk.set_user(None) + + # ------ add generic non-identifiable hardware metrics ------- + sentry_sdk.set_context( + "nanolayer.python", + { + # uname -a like + "platform.uname": str(platform.uname()), + # num of cores + "os.cpu_count": os.cpu_count(), + # 4GB 8GB 16GB etc + "psutil.virtual_memory": str(psutil.virtual_memory()), + }, + ) + os_release = EnvFile.parse("/etc/os-release") + # x86_64 / ARM + sentry_sdk.set_tag("nanolayer.arch", platform.machine()) + # ubuntu / debian / alpine / fedora etc + sentry_sdk.set_tag("nanolayer.os_release.ID", os_release.get("ID", None)) + # debian for both ubuntu and debian, etc + sentry_sdk.set_tag("nanolayer.os_release.ID_LIKE", os_release.get("ID_LIKE", None)) + # ubuntu 18.04 20.04 22.04 etc + sentry_sdk.set_tag( + "nanolayer.os_release.VERSION_ID", os_release.get("VERSION_ID", None) + ) + # true if nanolayer is being used as a binary, false otherwise + sentry_sdk.set_tag("nanolayer.binary_mode", "__file__" not in globals()) +except Exception as e: # no qa + logger.warning("usage metrics are disabled") diff --git a/nanolayer/installers/gh_release/gh_release_installer.py b/nanolayer/installers/gh_release/gh_release_installer.py index 36491bc6..9e445396 100644 --- a/nanolayer/installers/gh_release/gh_release_installer.py +++ b/nanolayer/installers/gh_release/gh_release_installer.py @@ -46,7 +46,7 @@ def get_file_members(self) -> List[str]: class ExtendedZipFile(ZipFile, AbstractExtendedArchive): def get_file_members(self) -> List[str]: members = self.namelist() - return [member for member in members if not member.endswith('/')] + return [member for member in members if not member.endswith("/")] def get_names_by_prefix(self, prefix: str) -> None: subdir_and_files = [ diff --git a/nanolayer/utils/linux_information_desk.py b/nanolayer/utils/linux_information_desk.py index a9e5e166..c7a68586 100644 --- a/nanolayer/utils/linux_information_desk.py +++ b/nanolayer/utils/linux_information_desk.py @@ -4,6 +4,17 @@ from typing import Dict +class EnvFile: + @staticmethod + def parse(path: str) -> Dict[str, str]: + with open(path, "r") as f: + return dict( + tuple(line.replace("\n", "").split("=")) + for line in f.readlines() + if not line.startswith("#") + ) + + class LinuxInformationDesk: OS_RELEASE_PATH = "/etc/os-release" @@ -58,15 +69,7 @@ class LinuxReleaseID(Enum): def _get_release_id_str(cls, id_like: bool = False) -> str: assert cls.has_root_privileges() - def _parse_env_file(path: str) -> Dict[str, str]: - with open(path, "r") as f: - return dict( - tuple(line.replace("\n", "").split("=")) - for line in f.readlines() - if not line.startswith("#") - ) - - parsed_os_release = _parse_env_file(cls.OS_RELEASE_PATH) + parsed_os_release = EnvFile.parse(cls.OS_RELEASE_PATH) if id_like: os_release_id = parsed_os_release.get("ID_LIKE", None) diff --git a/nanolayer/utils/settings.py b/nanolayer/utils/settings.py index 94604465..b2e113ff 100644 --- a/nanolayer/utils/settings.py +++ b/nanolayer/utils/settings.py @@ -11,10 +11,18 @@ class Config: cli_location: str = "" propagate_cli_location: str = "1" force_cli_installation: str = "" + + analytics_id: str = "https://2a5d4cc20cb94a8cbb691df3bcc69f0f@o4504983808901120.ingest.sentry.io/4504983813685248" + no_analytics: bool = False + verbose: str = "" ENV_CLI_LOCATION = f"{NanolayerSettings.Config.env_prefix}CLI_LOCATION" + +ENV_NO_ANALYTICS = f"{NanolayerSettings.Config.env_prefix}NO_ANALYTICS" +ENV_ANALYTICS_ID = f"{NanolayerSettings.Config.env_prefix}ANALYTICS_ID" + ENV_PROPAGATE_CLI_LOCATION = ( f"{NanolayerSettings.Config.env_prefix}PROPAGATE_CLI_LOCATION" ) diff --git a/requirements.txt b/requirements.txt index 32ebba11..337b9fff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ pydantic==1.10.5 typer invoke -semver==2.13.0 \ No newline at end of file +semver==2.13.0 +sentry-sdk>=1.19.1 \ No newline at end of file diff --git a/tests/installers/gh_release/test_gh_release_installer.py b/tests/installers/gh_release/test_gh_release_installer.py index a17c07b7..7a2279b1 100644 --- a/tests/installers/gh_release/test_gh_release_installer.py +++ b/tests/installers/gh_release/test_gh_release_installer.py @@ -8,7 +8,7 @@ @pytest.mark.parametrize( "test_command,excpected_result,image,repo,target,docker_platform", [ - ( + ( "upx --version", 0, "mcr.microsoft.com/devcontainers/base:debian", @@ -24,7 +24,7 @@ "doctl", "linux/amd64", ), - (# classic + ( # classic "argocd --help", 0, "mcr.microsoft.com/devcontainers/base:debian", @@ -48,7 +48,7 @@ "argocd", "linux/arm64", ), - ( # two binaries at same repo + ( # two binaries at same repo "which kubectx", 0, "mcr.microsoft.com/devcontainers/base:debian", @@ -56,7 +56,7 @@ "kubectx", "linux/amd64", ), - ( # two binaries at same repo + ( # two binaries at same repo "which kubens", 0, "mcr.microsoft.com/devcontainers/base:debian",