From 6a5505e729ab4489628c3e1dc7638f99b3cedea6 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Mon, 16 Dec 2024 17:58:14 +0100 Subject: [PATCH] feat: add cicd provider detector Related to MRGFY-4457 Change-Id: I39965fbdb72833c4e371b5bf345a24e3a2f91875 --- pytest_mergify/__init__.py | 11 +++++- pytest_mergify/resources/__init__.py | 0 pytest_mergify/resources/ci.py | 19 ++++++++++ pytest_mergify/resources/github_actions.py | 44 ++++++++++++++++++++++ pytest_mergify/utils.py | 3 ++ tests/test_resources.py | 12 ++++++ 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 pytest_mergify/resources/__init__.py create mode 100644 pytest_mergify/resources/ci.py create mode 100644 pytest_mergify/resources/github_actions.py create mode 100644 tests/test_resources.py diff --git a/pytest_mergify/__init__.py b/pytest_mergify/__init__.py index 0d147c3..d1f5db1 100644 --- a/pytest_mergify/__init__.py +++ b/pytest_mergify/__init__.py @@ -15,9 +15,10 @@ from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( OTLPSpanExporter, ) +import opentelemetry.sdk.resources from pytest_mergify import utils - +import pytest_mergify.resources.ci as resources_ci import pytest_opentelemetry.instrumentation @@ -74,7 +75,13 @@ def pytest_configure(self, config: _pytest.config.Config) -> None: else: return - tracer_provider = TracerProvider() + resource = opentelemetry.sdk.resources.get_aggregated_resources( + [ + resources_ci.CIResourceDetector(), + ] + ) + + tracer_provider = TracerProvider(resource=resource) tracer_provider.add_span_processor(span_processor) diff --git a/pytest_mergify/resources/__init__.py b/pytest_mergify/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytest_mergify/resources/ci.py b/pytest_mergify/resources/ci.py new file mode 100644 index 0000000..0d62c29 --- /dev/null +++ b/pytest_mergify/resources/ci.py @@ -0,0 +1,19 @@ +from opentelemetry.sdk.resources import Resource, ResourceDetector + +from pytest_mergify import utils + + +class CIResourceDetector(ResourceDetector): + """Detects OpenTelemetry Resource attributes for GitHub Actions.""" + + def detect(self) -> Resource: + provider = utils.get_ci_provider() + + if provider is None: + return Resource({}) + + return Resource( + { + "cicd.provider.name": provider, + } + ) diff --git a/pytest_mergify/resources/github_actions.py b/pytest_mergify/resources/github_actions.py new file mode 100644 index 0000000..bb8f6b5 --- /dev/null +++ b/pytest_mergify/resources/github_actions.py @@ -0,0 +1,44 @@ +import os +import subprocess +from typing import Dict, Union + +from opentelemetry.sdk.resources import Resource, ResourceDetector +from opentelemetry.semconv.resource import ResourceAttributes + +Attributes = Dict[str, Union[str, bool, int, float]] + + +class GitHubActionsResourceDetector(ResourceDetector): + """Detects OpenTelemetry Resource attributes for GitHub Actions.""" + + @staticmethod + def get_codebase_name() -> str: + # TODO: any better ways to guess the name of the codebase? + # TODO: look into methods for locating packaging information + return os.path.split(os.getcwd())[-1] + + @staticmethod + def get_codebase_version() -> str: + try: + response = subprocess.check_output( + ["git", "rev-parse", "--is-inside-work-tree"] + ) + if response.strip() != b"true": + return "[unknown: not a git repository]" + except Exception: # pylint: disable=broad-except + return "[unknown: not a git repository]" + + try: + version = subprocess.check_output(["git", "rev-parse", "HEAD"]) + except Exception as exception: # pylint: disable=broad-except + return f"[unknown: {str(exception)}]" + + return version.decode().strip() + + def detect(self) -> Resource: + return Resource( + { + ResourceAttributes.SERVICE_NAME: self.get_codebase_name(), + ResourceAttributes.SERVICE_VERSION: self.get_codebase_version(), + } + ) diff --git a/pytest_mergify/utils.py b/pytest_mergify/utils.py index 39d0e9a..8756bd4 100644 --- a/pytest_mergify/utils.py +++ b/pytest_mergify/utils.py @@ -1,11 +1,14 @@ import os import typing +from pytest_mergify import utils + CIProviderT = typing.Literal["github_actions", "circleci"] SUPPORTED_CIs = { "GITHUB_ACTIONS": "github_actions", "CIRCLECI": "circleci", + "_PYTEST_MERGIFY_TEST": "pytest_mergify_suite", } diff --git a/tests/test_resources.py b/tests/test_resources.py new file mode 100644 index 0000000..c045863 --- /dev/null +++ b/tests/test_resources.py @@ -0,0 +1,12 @@ +import _pytest.pytester +import _pytest.config + + +def test_span_resources_attributes( + pytestconfig: _pytest.config.Config, +) -> None: + plugin = pytestconfig.pluginmanager.get_plugin("PytestMergify") + assert plugin is not None + assert plugin.exporter is not None + spans = plugin.exporter.get_finished_spans() + assert spans[0].resource.attributes["cicd.provider.name"] == "pytest_mergify_suite"