Skip to content

Commit bdec192

Browse files
authored
feat: add jenkins support (#147)
Fixes MRGFY-5591
1 parent 1860cf3 commit bdec192

File tree

4 files changed

+113
-11
lines changed

4 files changed

+113
-11
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import os
2+
from opentelemetry.sdk.resources import Resource, ResourceDetector
3+
4+
from opentelemetry.semconv._incubating.attributes import cicd_attributes
5+
from opentelemetry.semconv._incubating.attributes import vcs_attributes
6+
7+
from pytest_mergify import utils
8+
9+
10+
class RepsoitoryNameExtractionError(Exception):
11+
pass
12+
13+
14+
class JenkinsResourceDetector(ResourceDetector):
15+
"""Detects OpenTelemetry Resource attributes for Jenkins."""
16+
17+
OPENTELEMETRY_JENKINS_MAPPING = {
18+
cicd_attributes.CICD_PIPELINE_NAME: (str, "JOB_NAME"),
19+
cicd_attributes.CICD_PIPELINE_RUN_ID: (str, "BUILD_ID"),
20+
"cicd.pipeline.run.url": (str, "BUILD_URL"),
21+
"cicd.pipeline.runner.name": (str, "NODE_NAME"),
22+
vcs_attributes.VCS_REF_HEAD_NAME: (str, "GIT_BRANCH"),
23+
vcs_attributes.VCS_REF_HEAD_REVISION: (str, "GIT_COMMIT"),
24+
vcs_attributes.VCS_REPOSITORY_URL_FULL: (str, "GIT_URL"),
25+
}
26+
27+
def detect(self) -> Resource:
28+
if utils.get_ci_provider() != "jenkins":
29+
return Resource({})
30+
31+
attributes = {}
32+
for attribute_name, (
33+
type_,
34+
envvar,
35+
) in self.OPENTELEMETRY_JENKINS_MAPPING.items():
36+
if envvar in os.environ:
37+
try:
38+
attributes[attribute_name] = type_(os.environ[envvar])
39+
except Exception:
40+
pass # skip invalid conversions
41+
42+
repository_name = utils._get_repository_name_from_env_url("GIT_URL")
43+
if repository_name:
44+
attributes["vcs.repository.name"] = repository_name
45+
46+
return Resource(attributes)

pytest_mergify/tracer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import pytest_mergify.resources.github_actions as resources_gha
1919
import pytest_mergify.resources.pytest as resources_pytest
2020
import pytest_mergify.resources.mergify as resources_mergify
21+
import pytest_mergify.resources.jenkins as resources_jenkins
2122

2223

2324
class SynchronousBatchSpanProcessor(export.SimpleSpanProcessor):
@@ -103,6 +104,7 @@ def __post_init__(self) -> None:
103104
[
104105
resources_ci.CIResourceDetector(),
105106
resources_gha.GitHubActionsResourceDetector(),
107+
resources_jenkins.JenkinsResourceDetector(),
106108
resources_pytest.PytestResourceDetector(),
107109
resources_mergify.MergifyResourceDetector(),
108110
]

pytest_mergify/utils.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
import re
33
import typing
44

5-
CIProviderT = typing.Literal["github_actions", "circleci", "pytest_mergify_suite"]
5+
CIProviderT = typing.Literal[
6+
"github_actions", "circleci", "pytest_mergify_suite", "jenkins"
7+
]
68

79
SUPPORTED_CIs: typing.Dict[str, CIProviderT] = {
810
"GITHUB_ACTIONS": "github_actions",
911
"CIRCLECI": "circleci",
12+
"JENKINS_URL": "jenkins",
1013
"_PYTEST_MERGIFY_TEST": "pytest_mergify_suite",
1114
}
1215

@@ -19,27 +22,42 @@ def is_in_ci() -> bool:
1922

2023
def get_ci_provider() -> typing.Optional[CIProviderT]:
2124
for envvar, name in SUPPORTED_CIs.items():
22-
if envvar in os.environ and strtobool(os.environ[envvar]):
23-
return name
25+
if envvar in os.environ:
26+
try:
27+
enabled = strtobool(os.environ[envvar])
28+
except ValueError:
29+
# Not a boolean, just check it's not empty
30+
enabled = bool(os.environ[envvar].strip())
31+
if enabled:
32+
return name
33+
34+
return None
35+
36+
37+
def _get_repository_name_from_env_url(env: str) -> typing.Optional[str]:
38+
repository_url = os.getenv(env)
39+
if repository_url and (
40+
match := re.match(
41+
r"(https?://[\w.-]+/)?(?P<full_name>[\w.-]+/[\w.-]+)/?$",
42+
repository_url,
43+
)
44+
):
45+
return match.group("full_name")
2446

2547
return None
2648

2749

2850
def get_repository_name() -> typing.Optional[str]:
2951
provider = get_ci_provider()
3052

53+
if provider == "jenkins":
54+
return _get_repository_name_from_env_url("GIT_URL")
55+
3156
if provider == "github_actions":
3257
return os.getenv("GITHUB_REPOSITORY")
3358

3459
if provider == "circleci":
35-
repository_url = os.getenv("CIRCLE_REPOSITORY_URL")
36-
if repository_url and (
37-
match := re.match(
38-
r"(https?://[\w.-]+/)?(?P<full_name>[\w.-]+/[\w.-]+)/?$",
39-
repository_url,
40-
)
41-
):
42-
return match.group("full_name")
60+
return _get_repository_name_from_env_url("CIRCLE_REPOSITORY_URL")
4361

4462
if provider == "pytest_mergify_suite":
4563
return "Mergifyio/pytest-mergify"

tests/test_resources.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,39 @@ def test_span_github_actions(
7676
span.resource.attributes["cicd.pipeline.runner.name"] == "self-hosted"
7777
for span in spans.values()
7878
)
79+
80+
81+
def test_span_jenkins(
82+
monkeypatch: pytest.MonkeyPatch,
83+
pytester_with_spans: conftest.PytesterWithSpanT,
84+
) -> None:
85+
monkeypatch.setenv("GITHUB_ACTIONS", "false")
86+
monkeypatch.setenv("JENKINS_URL", "https://jenkins.example.com")
87+
monkeypatch.setenv(
88+
"BUILD_URL", "https://jenkins.example.com/Mergifyio/pytest-mergify"
89+
)
90+
monkeypatch.setenv("BUILD_ID", "jenkins-job-name#5")
91+
monkeypatch.setenv("JOB_NAME", "jenkins-job-name")
92+
monkeypatch.setenv("GIT_URL", "https://github.com/Mergifyio/pytest-mergify")
93+
monkeypatch.setenv("GIT_BRANCH", "main")
94+
monkeypatch.setenv("GIT_COMMIT", "1860cf377dd5610e256ff52e47cf38816cc04549")
95+
monkeypatch.setenv("NODE_NAME", "self-hosted")
96+
result, spans = pytester_with_spans()
97+
assert spans is not None
98+
assert all(
99+
span.resource.attributes["vcs.repository.name"] == "Mergifyio/pytest-mergify"
100+
for span in spans.values()
101+
)
102+
assert all(
103+
span.resource.attributes["vcs.repository.url.full"]
104+
== "https://github.com/Mergifyio/pytest-mergify"
105+
for span in spans.values()
106+
)
107+
assert all(
108+
span.resource.attributes["cicd.pipeline.run.id"] == "jenkins-job-name#5"
109+
for span in spans.values()
110+
)
111+
assert all(
112+
span.resource.attributes["cicd.pipeline.runner.name"] == "self-hosted"
113+
for span in spans.values()
114+
)

0 commit comments

Comments
 (0)