Skip to content

Commit bfe0e22

Browse files
avaid-crestWyrinedkirov-dd
authored
DDSaas: Sonatype Nexus v1.0.1: Added support of Sonatype Nexus version 3.79.0 (#20045)
* Pinned sonatype nexus docker image version * Resolved ci failure * Added changelog entry * Renamed the changelog.d file * Modified Changelogs * Modified md file related to changelog * Added changes as per review comments * Adding formatting changes * Removed extra vertical spacing * Rename 20045.added.md to 20045.added * Update sonatype_nexus/CHANGELOG.md The CHANGELOG.md gets updated automatically upon release * Apply suggestions from code review --------- Co-authored-by: Kirolos Shahat <kashahat@gmail.com> Co-authored-by: dkirov-dd <166512750+dkirov-dd@users.noreply.github.com>
1 parent 15f7d6e commit bfe0e22

File tree

6 files changed

+123
-124
lines changed

6 files changed

+123
-124
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added support for Sonatype Nexus version 3.79.0

sonatype_nexus/datadog_checks/sonatype_nexus/check.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,21 @@ def create_metric_for_configs(self, metric_data: dict, metric_name: str):
8585
tag_list = []
8686
for tag_key in config["tag_key"]:
8787
tag_list.append(f"{tag_key}:{item[tag_key]}")
88-
self.gauge(metric_name, int(item[config["value_key"]]), base_tags + tag_list, hostname=None)
88+
self.gauge(metric_name, int(item.get(config["value_key"], 0)), base_tags + tag_list, hostname=None)
8989
elif isinstance(value, int):
9090
self.gauge(metric_name, int(value), base_tags, hostname=None)
9191
elif isinstance(value, dict):
92-
self.gauge(metric_name, int(value[config["value_key"]]), base_tags, hostname=None)
92+
self.gauge(metric_name, int(value.get(config["value_key"], 0)), base_tags, hostname=None)
9393

9494
def create_metric_for_configs_by_format_type(self, metric_data, metric_name, metric_info):
9595
base_tags = [f"sonatype_host:{self.extract_ip_from_url()}"] + (self.custom_tags if self.custom_tags else [])
9696

9797
if isinstance(metric_data, list):
9898
for item in metric_data:
9999
for format_type, data in item.items():
100-
self.ingest_metric(base_tags, format_type, metric_info, metric_name, data[metric_info["value_key"]])
100+
self.ingest_metric(
101+
base_tags, format_type, metric_info, metric_name, data.get(metric_info["value_key"], 0)
102+
)
101103
elif isinstance(metric_data, dict):
102104
for format_type, metric_value in metric_data.items():
103105
self.ingest_metric(base_tags, format_type, metric_info, metric_name, metric_value)

sonatype_nexus/tests/compose/docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
nexus:
3-
image: sonatype/nexus3:3.75.1
3+
image: sonatype/nexus3:3.79.0
44
container_name: nexus
55
ports:
66
- "8081:8081"

sonatype_nexus/tests/conftest.py

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,84 @@
22
# All rights reserved
33
# Licensed under a 3-clause BSD style license (see LICENSE)
44
import os
5+
import time
56
from copy import deepcopy
67

78
import pytest
89

9-
from datadog_checks.dev import docker_run
10+
from datadog_checks.dev import docker_run, run_command
1011

1112
from .common import COMPOSE, INSTANCE
1213

14+
instance = {
15+
"username": "test_username",
16+
"password": "test_password",
17+
"min_collection_interval": 400,
18+
"server_url": "https://example.com",
19+
}
20+
21+
22+
def get_nexus_password(max_retries=5, retry_interval=10, password_wait_retries=30, password_wait_interval=10):
23+
try:
24+
run_command("docker run -d -p 8081:8081 --name sonatype_nexus_38 sonatype/nexus3:3.79.0")
25+
except Exception as e:
26+
print(f"Note: {e}")
27+
28+
container_id = None
29+
for attempt in range(max_retries):
30+
try:
31+
container_id = run_command(
32+
"docker ps --filter 'ancestor=sonatype/nexus3:3.79.0' --format '{{.ID}}'", capture=True
33+
).stdout.strip()
34+
35+
if container_id:
36+
print(f"Found Nexus container with ID: {container_id}")
37+
break
38+
39+
print(f"Waiting for Nexus container to start... (attempt {attempt + 1}/{max_retries})")
40+
time.sleep(retry_interval)
41+
42+
except Exception as e:
43+
print(f"Error checking container status: {e}")
44+
time.sleep(retry_interval)
45+
46+
if not container_id:
47+
print("Error: No running Nexus container found after maximum retry attempts.")
48+
exit(1)
49+
50+
password = None
51+
for attempt in range(password_wait_retries):
52+
try:
53+
file_check = run_command(
54+
f"docker exec {container_id} sh -c '"
55+
"[ -f /opt/sonatype/sonatype-work/nexus3/admin.password ] "
56+
"&& echo \"exists\" || echo \"not exists\"'",
57+
capture=True,
58+
).stdout.strip()
59+
60+
if file_check == "exists":
61+
password = run_command(
62+
f"docker exec {container_id} sh -c 'cat /opt/sonatype/sonatype-work/nexus3/admin.password'",
63+
capture=True,
64+
).stdout.strip()
65+
66+
if password:
67+
print(f"Successfully retrieved password after {attempt + 1} attempts")
68+
break
69+
70+
print(f"Password file not ready yet (attempt {attempt + 1}/{password_wait_retries})")
71+
time.sleep(password_wait_interval)
72+
73+
except Exception as e:
74+
print(f"Error checking password file (attempt {attempt + 1}/{password_wait_retries}): {e}")
75+
time.sleep(password_wait_interval)
76+
77+
if not password:
78+
print("Error: Could not retrieve Nexus password after maximum retry attempts.")
79+
exit(1)
80+
81+
return password
82+
1383

1484
@pytest.fixture(scope='session')
1585
def dd_environment():
@@ -20,9 +90,7 @@ def dd_environment():
2090
build=True,
2191
sleep=30,
2292
):
23-
yield deepcopy(INSTANCE)
24-
25-
26-
@pytest.fixture
27-
def instance():
28-
return deepcopy(INSTANCE)
93+
password = get_nexus_password()
94+
modified_instance = deepcopy(INSTANCE)
95+
modified_instance["password"] = password
96+
yield modified_instance

sonatype_nexus/tests/test_check.py

Lines changed: 39 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from datadog_checks.sonatype_nexus.check import SonatypeNexusCheck
1010
from datadog_checks.sonatype_nexus.errors import EmptyResponseError
1111

12+
from .conftest import instance
13+
1214

1315
@pytest.fixture
1416
def mock_http_response(mocker):
@@ -26,19 +28,50 @@ def test_successful_metrics_collection(dd_run_check, mock_http_response, aggrega
2628
json_data=status_metrics_response_data.update({"gauges": {"jvm.memory.heap.used": {"value": 123456789}}}),
2729
)
2830

29-
instance = {
30-
"username": "test_username",
31-
"password": "test_password",
32-
"min_collection_interval": 400,
33-
"server_url": "https://example.com",
34-
}
3531
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
3632
dd_run_check(check)
3733

3834
aggregator.assert_all_metrics_covered()
3935
aggregator.assert_metrics_using_metadata(get_metadata_metrics())
4036

4137

38+
def test_create_metric_for_configs_int(mocker, aggregator):
39+
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
40+
metric_data = {"value": 100}
41+
metric_name = "analytics.total_memory"
42+
43+
check.create_metric_for_configs(metric_data, metric_name)
44+
45+
aggregator.assert_metric(
46+
"sonatype_nexus.analytics.total_memory",
47+
)
48+
49+
50+
def test_create_metric_for_configs_dict(mocker, aggregator):
51+
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
52+
metric_data = {"value": {"total_count": 200}}
53+
metric_name = "analytics.malicious_risk_on_disk"
54+
55+
check.create_metric_for_configs(metric_data, metric_name)
56+
57+
aggregator.assert_metric(
58+
"sonatype_nexus.analytics.malicious_risk_on_disk",
59+
)
60+
61+
62+
def test_create_metric_for_configs_by_format_type_list(mocker, aggregator):
63+
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
64+
metric_data = [{"maven": {"bytes_uploaded": 100}}]
65+
metric_name = "analytics.uploaded_bytes_by_format"
66+
metric_info = constants.METRIC_CONFIGS_BY_FORMAT_TYPE[metric_name]
67+
68+
check.create_metric_for_configs_by_format_type(metric_data, metric_name, metric_info)
69+
70+
aggregator.assert_metric(
71+
f"sonatype_nexus.{metric_name}",
72+
)
73+
74+
4275
def test_empty_instance(dd_run_check):
4376
with pytest.raises(
4477
Exception,
@@ -75,13 +108,6 @@ def test_bad_request_error(dd_run_check, mock_http_response):
75108
json_data={"error": "Bad request"},
76109
)
77110

78-
instance = {
79-
"username": "test_user",
80-
"password": "test_password",
81-
"min_collection_interval": 400,
82-
"server_url": "https://example.com",
83-
}
84-
85111
with pytest.raises(Exception) as excinfo:
86112
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
87113
dd_run_check(check)
@@ -95,13 +121,6 @@ def test_license_expired_error(dd_run_check, mock_http_response):
95121
json_data={"error": "License expired"},
96122
)
97123

98-
instance = {
99-
"username": "test_user",
100-
"password": "test_password",
101-
"min_collection_interval": 400,
102-
"server_url": "https://example.com",
103-
}
104-
105124
with pytest.raises(Exception) as excinfo:
106125
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
107126
dd_run_check(check)
@@ -115,13 +134,6 @@ def test_insufficient_permission_error(dd_run_check, mock_http_response):
115134
json_data={"error": "Insufficient permissions"},
116135
)
117136

118-
instance = {
119-
"username": "test_user",
120-
"password": "test_password",
121-
"min_collection_interval": 400,
122-
"server_url": "https://example.com",
123-
}
124-
125137
with pytest.raises(Exception) as excinfo:
126138
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
127139
dd_run_check(check)
@@ -135,13 +147,6 @@ def test_not_found_error(dd_run_check, mock_http_response):
135147
json_data={"error": "Resource not found"},
136148
)
137149

138-
instance = {
139-
"username": "test_user",
140-
"password": "test_password",
141-
"min_collection_interval": 400,
142-
"server_url": "https://example.com",
143-
}
144-
145150
with pytest.raises(Exception) as excinfo:
146151
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
147152
dd_run_check(check)
@@ -155,13 +160,6 @@ def test_server_error(dd_run_check, mock_http_response):
155160
json_data={"error": "Internal server error"},
156161
)
157162

158-
instance = {
159-
"username": "test_user",
160-
"password": "test_password",
161-
"min_collection_interval": 400,
162-
"server_url": "https://example.com",
163-
}
164-
165163
with pytest.raises(Exception) as excinfo:
166164
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
167165
dd_run_check(check)
@@ -175,13 +173,6 @@ def test_timeout_error(dd_run_check, mock_http_response):
175173
json_data={"error": "TimeoutError"},
176174
)
177175

178-
instance = {
179-
"username": "test_user",
180-
"password": "test_password",
181-
"min_collection_interval": 400,
182-
"server_url": "https://example.com",
183-
}
184-
185176
with pytest.raises(Exception) as excinfo:
186177
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
187178
dd_run_check(check)
@@ -196,13 +187,6 @@ def test_empty_response_error(dd_run_check, mocker):
196187
side_effect=EmptyResponseError(),
197188
)
198189

199-
instance = {
200-
"username": "test_user",
201-
"password": "test_password",
202-
"min_collection_interval": 400,
203-
"server_url": "https://example.com",
204-
}
205-
206190
with pytest.raises(Exception) as excinfo:
207191
check = SonatypeNexusCheck("sonatype_nexus", {}, [instance])
208192
dd_run_check(check)

sonatype_nexus/tests/test_e2e.py

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,10 @@
11
# (C) Datadog, Inc. 2025-present
22
# All rights reserved
33
# Licensed under a 3-clause BSD style license (see LICENSE)
4-
import time
5-
64
import pytest
75

8-
from datadog_checks.dev import run_command
9-
10-
11-
def get_nexus_password(max_retries=5, retry_interval=10):
12-
try:
13-
run_command("docker run -d -p 8081:8081 --name sonatype_nexus_3 sonatype/nexus3")
14-
except Exception as e:
15-
print(f"Note: {e}")
16-
17-
container_id = None
18-
for attempt in range(max_retries):
19-
try:
20-
container_id = run_command(
21-
"docker ps --filter 'ancestor=sonatype/nexus3' --format '{{.ID}}'", capture=True
22-
).stdout.strip()
23-
24-
if container_id:
25-
print(f"Found Nexus container with ID: {container_id}")
26-
break
27-
28-
print(f"Waiting for Nexus container to start... (attempt {attempt+1}/{max_retries})")
29-
time.sleep(retry_interval)
30-
31-
except Exception as e:
32-
print(f"Error checking container status: {e}")
33-
time.sleep(retry_interval)
34-
35-
if not container_id:
36-
print("Error: No running Nexus container found after maximum retry attempts.")
37-
exit(1)
38-
39-
password = None
40-
time.sleep(45)
41-
for attempt in range(max_retries):
42-
try:
43-
password_file = "/opt/sonatype/sonatype-work/nexus3/admin.password"
44-
password = run_command(
45-
f"docker exec {container_id} sh -c 'cat {password_file}'", capture=True
46-
).stdout.strip()
47-
48-
if password:
49-
break
50-
51-
except Exception as e:
52-
print(f"Password file not ready yet (attempt {attempt+1}/{max_retries}): {e}")
53-
time.sleep(retry_interval)
54-
55-
if not password:
56-
print("Error: Could not retrieve Nexus password after maximum retry attempts.")
57-
exit(1)
58-
59-
return password
60-
616

627
@pytest.mark.e2e
63-
def test_e2e(dd_agent_check, instance, aggregator):
64-
instance["password"] = get_nexus_password()
65-
aggregator = dd_agent_check(instance)
8+
def test_e2e(dd_agent_check, aggregator):
9+
aggregator = dd_agent_check()
6610
aggregator.assert_metric('sonatype_nexus.status.available_cpus_health', value=1)

0 commit comments

Comments
 (0)