Skip to content

Commit ca0a0e1

Browse files
authored
Updating validation for the Sysdig public API url (#10)
# Updating regex validation for the Sysdig public API url ## Changes * Updating regex validation for the Sysdig public API url * Adding tests for the different URL formats Client is expecting the public API URL in the format https://api.{region}.sysdig.com. We will check the following: - A valid Sysdig host URL is provided by matching the expected patterns with a regex. - If not, we will try to fetch the public API URL from the app config yaml 'sysdig.public_api_url'. - If neither is available, we will raise an error. --------- Signed-off-by: S3B4SZ17 <[email protected]>
1 parent 05dee46 commit ca0a0e1

File tree

7 files changed

+101
-17
lines changed

7 files changed

+101
-17
lines changed

app_config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ app:
66

77
sysdig:
88
host: "https://us2.app.sysdig.com"
9+
# public_api_url: "https://<YOUR_CUSTOM_PUBLIC_API_URL>"
910

1011
mcp:
1112
transport: stdio

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "sysdig-mcp-server"
3-
version = "0.1.3"
3+
version = "0.1.4"
44
description = "Sysdig MCP Server"
55
readme = "README.md"
66
requires-python = ">=3.12"

tests/config_test.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
Testing overall configuration of the MCP server.
3+
"""
4+
5+
6+
def test_api_url_format() -> None:
7+
"""
8+
Test that the API URL is formatted correctly.
9+
This test checks if the public API URL is constructed properly
10+
when the old API format is not used.
11+
"""
12+
from utils.sysdig.client_config import _get_public_api_url
13+
14+
# URL regions refer to https://docs.sysdig.com/en/administration/saas-regions-and-ip-ranges/
15+
region_urls = {
16+
"us1": {"url": "https://secure.sysdig.com", "public_url": "https://api.us1.sysdig.com"},
17+
"us2": {"url": "https://us2.app.sysdig.com", "public_url": "https://api.us2.sysdig.com"},
18+
"eu1": {"url": "https://eu1.app.sysdig.com", "public_url": "https://api.eu1.sysdig.com"},
19+
"au1": {"url": "https://app.au1.sysdig.com", "public_url": "https://api.au1.sysdig.com"},
20+
"me2": {"url": "https://app.me2.sysdig.com", "public_url": "https://api.me2.sysdig.com"},
21+
# Edge case that does not follow the standard pattern it should return the same passed URL
22+
"edge": {"url": "https://edge.something.com", "public_url": "https://edge.something.com"},
23+
}
24+
25+
# Check if the public API URL is formatted correctly
26+
public_api_us1 = _get_public_api_url(region_urls["us1"]["url"])
27+
public_api_us2 = _get_public_api_url(region_urls["us2"]["url"])
28+
public_api_eu1 = _get_public_api_url(region_urls["eu1"]["url"])
29+
public_api_au1 = _get_public_api_url(region_urls["au1"]["url"])
30+
public_api_me2 = _get_public_api_url(region_urls["me2"]["url"])
31+
public_api_edge = _get_public_api_url(region_urls["edge"]["url"])
32+
33+
assert public_api_us1 == region_urls["us1"]["public_url"], (
34+
f"Expected {region_urls['us1']['public_url']}, got {public_api_us1}"
35+
)
36+
assert public_api_us2 == region_urls["us2"]["public_url"], (
37+
f"Expected {region_urls['us2']['public_url']}, got {public_api_us2}"
38+
)
39+
assert public_api_eu1 == region_urls["eu1"]["public_url"], (
40+
f"Expected {region_urls['eu1']['public_url']}, got {public_api_eu1}"
41+
)
42+
assert public_api_au1 == region_urls["au1"]["public_url"], (
43+
f"Expected {region_urls['au1']['public_url']}, got {public_api_au1}"
44+
)
45+
assert public_api_me2 == region_urls["me2"]["public_url"], (
46+
f"Expected {region_urls['me2']['public_url']}, got {public_api_me2}"
47+
)
48+
assert not public_api_edge, f"Expected empty string, got {public_api_edge}"
49+
print("All public API URLs are formatted correctly.")

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ def mock_creds():
4343
Fixture to set up mocked credentials.
4444
"""
4545
os.environ["SYSDIG_SECURE_TOKEN"] = "mocked_token"
46-
os.environ["SYSDIG_HOST"] = "https://mocked.secure"
46+
os.environ["SYSDIG_HOST"] = "https://us2.app.sysdig.com"

tests/events_feed_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ def test_get_event_info(mock_success_response: MagicMock | AsyncMock, mock_creds
2323
mock_success_response (MagicMock | AsyncMock): Mocked response object.
2424
mock_creds: Mocked credentials.
2525
"""
26-
26+
# Override the environment variable for MCP transport
27+
os.environ["MCP_TRANSPORT"] = "stdio"
2728
# Successful response
2829
mock_success_response.return_value.json.return_value = EVENT_INFO_RESPONSE
2930
mock_success_response.return_value.status_code = HTTPStatus.OK

utils/sysdig/client_config.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88
import re
99
from typing import Optional
1010

11+
# Application config loader
12+
from utils.app_config import get_app_config
13+
1114
# Set up logging
1215
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=os.environ.get("LOGLEVEL", "ERROR"))
1316
log = logging.getLogger(__name__)
1417

18+
app_config = get_app_config()
19+
1520

1621
# Lazy-load the Sysdig client configuration
1722
def get_configuration(
@@ -22,18 +27,36 @@ def get_configuration(
2227
2328
Args:
2429
token (str): The Sysdig Secure token.
25-
sysdig_host_url (str): The base URL of the Sysdig API.
26-
old_api (bool): If True, uses the old Sysdig API URL format. Defaults to False.
30+
sysdig_host_url (str): The base URL of the Sysdig API,
31+
refer to the docs https://docs.sysdig.com/en/administration/saas-regions-and-ip-ranges/#sysdig-platform-regions.
32+
old_api (bool): If True, uses the old Sysdig API URL format.
33+
Defaults to False using the public API URL format https://api.{region}.sysdig.com.
2734
Returns:
2835
sysdig_client.Configuration: A configured Sysdig client instance.
36+
Raises:
37+
ValueError: If the Sysdig host URL is not provided or is invalid.
2938
"""
3039
# Check if the token and sysdig_host_url are provided, otherwise fetch from environment variables
3140
if not token and not sysdig_host_url:
3241
env_vars = get_api_env_vars()
3342
token = env_vars["SYSDIG_SECURE_TOKEN"]
3443
sysdig_host_url = env_vars["SYSDIG_HOST"]
3544
if not old_api:
45+
"""
46+
Client expecting the public API URL in the format https://api.{region}.sysdig.com. We will check the following:
47+
- A valid Sysdig host URL is provided by matching the expected patterns with a regex.
48+
- If not, we will try to fetch the public API URL from the app config yaml 'sysdig.public_api_url'.
49+
- If neither is available, we will raise an error.
50+
"""
3651
sysdig_host_url = _get_public_api_url(sysdig_host_url)
52+
if not sysdig_host_url:
53+
sysdig_host_url = app_config.get("sysdig", {}).get("public_api_url")
54+
if not sysdig_host_url:
55+
raise ValueError(
56+
"No valid Sysdig public API URL found. Please check your Sysdig host URL or"
57+
"explicitly set the public API URL in the app config 'sysdig.public_api_url'."
58+
"The expected format is https://api.{region}.sysdig.com."
59+
)
3760
log.info(f"Using public API URL: {sysdig_host_url}")
3861

3962
configuration = sysdig_client.Configuration(
@@ -67,20 +90,30 @@ def get_api_env_vars() -> dict:
6790

6891
def _get_public_api_url(base_url: str) -> str:
6992
"""
70-
Get the public API URL from the base URL.
93+
Maps a Sysdig base URL to its corresponding public API URL.
94+
This function extracts the region from the base URL and constructs the public API URL in the format
95+
https://api.{region}.sysdig.com.
96+
97+
If the base URL does not match any known patterns, it returns an empty string.
7198
7299
Args:
73100
base_url: The base URL of the Sysdig API
74101
75102
Returns:
76-
str: The public API URL in the format https://api.<region>.sysdig.com
103+
str: The public API URL in the format https://api.{region}.sysdig.com
77104
"""
78-
# Regex to capture the region pattern (like us2, us3, au1, etc.)
79-
# This assumes the region is a subdomain that starts with 2 lowercase letters and ends with a digit
80-
pattern = re.search(r"https://(?:(?P<region1>[a-z]{2}\d)\.app|app\.(?P<region2>[a-z]{2}\d))\.sysdig\.com", base_url)
81-
if pattern:
82-
region = pattern.group("region1") or pattern.group("region2") # Extract the region
83-
return f"https://api.{region}.sysdig.com"
84-
else:
85-
# Edge case for the secure API URL that is us1
86-
return "https://api.us1.sysdig.com"
105+
106+
patterns = [
107+
(r"^https://secure\.sysdig\.com$", lambda m: "us1"),
108+
(r"^https://([a-z]{2}\d)\.app\.sysdig\.com$", lambda m: m.group(1)),
109+
(r"^https://app\.([a-z]{2}\d)\.sysdig\.com$", lambda m: m.group(1)),
110+
]
111+
112+
for pattern, region_fn in patterns:
113+
match = re.match(pattern, base_url)
114+
if match:
115+
region = region_fn(match)
116+
return f"https://api.{region}.sysdig.com"
117+
118+
log.warning("A not recognized Sysdig URL was provided, returning an empty string. This may lead to unexpected behavior.")
119+
return ""

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)