Skip to content

Commit 4668cc7

Browse files
committed
feat Added prefix to env vars and fixed convention of return outputs
Signed-off-by: S3B4SZ17 <[email protected]>
1 parent 0ca3eba commit 4668cc7

File tree

18 files changed

+240
-200
lines changed

18 files changed

+240
-200
lines changed

.github/workflows/test.yaml

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -45,57 +45,3 @@ jobs:
4545

4646
- name: Run Unit Tests
4747
run: make test
48-
49-
check_version:
50-
name: Check Version
51-
runs-on: ubuntu-latest
52-
needs: test
53-
permissions:
54-
contents: write # required for creating a tag
55-
steps:
56-
- name: Check out repository
57-
uses: actions/checkout@v4
58-
with:
59-
ref: ${{ github.sha }} # required for better experience using pre-releases
60-
fetch-depth: '0' # Required due to the way Git works, without it this action won't be able to find any or the correct tags
61-
62-
- name: Extract current version
63-
id: pyproject_version
64-
run: |
65-
TAG=v$(grep 'version =' pyproject.toml | sed -e 's/version = "\(.*\)"/\1/')
66-
echo "TAG=$TAG" >> "$GITHUB_OUTPUT"
67-
68-
- name: Get branch ref name
69-
id: branch_ref
70-
run: |
71-
BRANCH_NAME=${{ github.base_ref || github.ref_name }}
72-
echo "$BRANCH_NAME"
73-
echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
74-
75-
- name: Get tag version
76-
id: semantic_release
77-
uses: anothrNick/[email protected]
78-
env:
79-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
80-
DEFAULT_BUMP: "patch"
81-
TAG_CONTEXT: 'repo'
82-
WITH_V: true
83-
DRY_RUN: true
84-
85-
- name: Compare versions
86-
run: |
87-
echo "Current version: ${{ steps.pyproject_version.outputs.TAG }}"
88-
echo "New version: ${{ steps.semantic_release.outputs.tag }}"
89-
if [ "${{ steps.pyproject_version.outputs.TAG }}" != "${{ steps.semantic_release.outputs.tag }}" ]; then
90-
echo "### Version mismatch detected! :warning:
91-
Current pyproject version: ${{ steps.pyproject_version.outputs.TAG }}
92-
New Tag version: **${{ steps.semantic_release.outputs.tag }}**
93-
Current Tag: ${{ steps.semantic_release.outputs.old_tag }}
94-
Please update the version in pyproject.toml." >> $GITHUB_STEP_SUMMARY
95-
exit 1
96-
else
97-
echo "### Version match confirmed! :rocket:
98-
Current pyproject version: ${{ steps.pyproject_version.outputs.TAG }}
99-
New Tag version: **${{ steps.semantic_release.outputs.tag }}**
100-
The version is up-to-date." >> $GITHUB_STEP_SUMMARY
101-
fi

main.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
log = logging.getLogger(__name__)
2828

2929

30-
31-
3230
def handle_signals():
3331
def signal_handler(sig, frame):
3432
log.info(f"Received signal {sig}, shutting down...")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ dependencies = [
1010
"pyyaml==6.0.2",
1111
"sqlalchemy==2.0.36",
1212
"sqlmodel==0.0.22",
13-
"sysdig-sdk-python @ git+https://github.com/sysdiglabs/sysdig-sdk-python@597285143188019cd0e86fde43f94b1139f5441d",
13+
"sysdig-sdk-python @ git+https://github.com/sysdiglabs/sysdig-sdk-python@852ee2ccad12a8b445dd4732e7f3bd44d78a37f7",
1414
"dask==2025.4.1",
1515
"oauthlib==3.2.2",
1616
"fastapi==0.116.1",

tests/conftest.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
import json
66
import pytest
77
import os
8-
from unittest.mock import patch
8+
from utils.app_config import AppConfig
9+
from unittest.mock import MagicMock, create_autospec, patch
10+
from fastmcp.server.context import Context
11+
from sysdig_client import SecureEventsApi, ApiClient, InventoryApi, VulnerabilityManagementApi
12+
from utils.sysdig.legacy_sysdig_api import LegacySysdigApi
13+
from fastmcp.server import FastMCP
914

1015

1116
def util_load_json(path):
@@ -26,7 +31,7 @@ def mock_success_response():
2631
Fixture to mock the urllib3.PoolManager.request method
2732
2833
Yields:
29-
MagicMock: A mocked request object that simulates a successful HTTP response.
34+
MagicMock: A mocked request object that simulates a successful HTTP response.
3035
"""
3136
with patch("urllib3.PoolManager.request") as mock_request:
3237
mock_resp = patch("urllib3.response.HTTPResponse").start()
@@ -42,5 +47,41 @@ def mock_creds():
4247
"""
4348
Fixture to set up mocked credentials.
4449
"""
45-
os.environ["SYSDIG_SECURE_TOKEN"] = "mocked_token"
46-
os.environ["SYSDIG_HOST"] = "https://us2.app.sysdig.com"
50+
os.environ["SYSDIG_MCP_SECURE_TOKEN"] = "mocked_token"
51+
os.environ["SYSDIG_MCP_HOST"] = "https://us2.app.sysdig.com"
52+
53+
54+
def mock_app_config() -> AppConfig:
55+
"""
56+
Utility function to create a mocked AppConfig instance.
57+
Returns:
58+
AppConfig: A mocked AppConfig instance.
59+
"""
60+
mock_cfg = create_autospec(AppConfig, instance=True)
61+
62+
mock_cfg.sysdig_endpoint.return_value = "https://us2.app.sysdig.com"
63+
mock_cfg.transport.return_value = "stdio"
64+
mock_cfg.log_level.return_value = "DEBUG"
65+
mock_cfg.port.return_value = 8080
66+
67+
return mock_cfg
68+
69+
70+
@pytest.fixture
71+
def mock_context() -> Context:
72+
"""
73+
Utility function to create a mocked FastMCP context.
74+
Returns:
75+
Context: A mocked FastMCP context.
76+
"""
77+
78+
ctx = Context(MagicMock(spec=FastMCP))
79+
80+
api_instances = {
81+
"secure_events": SecureEventsApi(ApiClient()),
82+
"vulnerability_management": VulnerabilityManagementApi(ApiClient()),
83+
"inventory": InventoryApi(ApiClient()),
84+
"legacy_sysdig_api": LegacySysdigApi(ApiClient()),
85+
}
86+
ctx.set_state("api_instances", api_instances)
87+
return ctx

tests/events_feed_test.py

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
Events Feed Test Module
33
"""
44

5+
import os
56
from http import HTTPStatus
67
from tools.events_feed.tool import EventsFeedTools
7-
from utils.app_config import AppConfig
8-
from .conftest import util_load_json
9-
from unittest.mock import MagicMock, AsyncMock, create_autospec
10-
from sysdig_client.api import SecureEventsApi
11-
import os
8+
from unittest.mock import MagicMock, AsyncMock
129
from fastmcp.server.context import Context
1310
from fastmcp.server import FastMCP
11+
from .conftest import util_load_json, mock_app_config
1412

1513
# Get the absolute path of the current module file
1614
module_path = os.path.abspath(__file__)
@@ -20,18 +18,10 @@
2018

2119
EVENT_INFO_RESPONSE = util_load_json(f"{module_directory}/test_data/events_feed/event_info_response.json")
2220

21+
ctx = Context(MagicMock(spec=FastMCP))
2322

24-
def mock_app_config() -> AppConfig:
25-
mock_cfg = create_autospec(AppConfig, instance=True)
26-
27-
mock_cfg.sysdig_endpoint.return_value = "https://us2.app.sysdig.com"
28-
mock_cfg.transport.return_value = "stdio"
29-
mock_cfg.log_level.return_value = "DEBUG"
30-
mock_cfg.port.return_value = 8080
31-
32-
return mock_cfg
3323

34-
def test_get_event_info(mock_success_response: MagicMock | AsyncMock, mock_creds) -> None:
24+
def test_get_event_info(mock_success_response: MagicMock | AsyncMock, mock_creds, mock_context: Context) -> None:
3525
"""Test the get_event_info tool method.
3626
Args:
3727
mock_success_response (MagicMock | AsyncMock): Mocked response object.
@@ -43,22 +33,8 @@ def test_get_event_info(mock_success_response: MagicMock | AsyncMock, mock_creds
4333

4434
tools_client = EventsFeedTools(app_config=mock_app_config())
4535

46-
ctx = Context(FastMCP())
47-
48-
# Seed FastMCP Context state with mocked API instances expected by the tools
49-
secure_events_api = MagicMock(spec=SecureEventsApi)
50-
# The tool returns whatever the SDK method returns; make it be our mocked HTTP response
51-
secure_events_api.get_event_v1_without_preload_content.return_value = mock_success_response.return_value
52-
53-
api_instances = {
54-
"secure_events": secure_events_api,
55-
# Not used by this test, but present in real runtime; keep as empty mock to avoid KeyErrors elsewhere
56-
"legacy_sysdig_api": MagicMock(),
57-
}
58-
ctx.set_state("api_instances", api_instances)
59-
6036
# Pass the mocked Context object
61-
result: dict = tools_client.tool_get_event_info(ctx=ctx, event_id="12345")
37+
result: dict = tools_client.tool_get_event_info(ctx=mock_context, event_id="12345")
6238
results: dict = result["results"]
6339

6440
assert result.get("status_code") == HTTPStatus.OK

tools/cli_scanner/tool.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111

1212
from utils.app_config import AppConfig
1313

14+
TMP_OUTPUT_FILE = "/tmp/sysdig_cli_scanner_output.json"
15+
16+
1417
class CLIScannerTool:
1518
"""
1619
A class to encapsulate the tools for interacting with the Sysdig CLI Scanner.
1720
"""
1821

1922
def __init__(self, app_config: AppConfig):
2023
self.app_config = app_config
21-
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=app_config.log_level())
2224
self.log = logging.getLogger(__name__)
2325
self.cmd: str = "sysdig-cli-scanner"
2426
self.default_args: list = [
@@ -146,7 +148,7 @@ def run_sysdig_cli_scanner(
146148

147149
try:
148150
# Run the command
149-
with open("sysdig_cli_scanner_output.json", "w") as output_file:
151+
with open(TMP_OUTPUT_FILE, "w") as output_file:
150152
result = subprocess.run(cmd, text=True, check=True, stdout=output_file, stderr=subprocess.PIPE)
151153
output_result = output_file.read()
152154
output_file.close()
@@ -168,15 +170,15 @@ def run_sysdig_cli_scanner(
168170
}
169171
raise Exception(result)
170172
else:
171-
with open("sysdig_cli_scanner_output.json", "r") as output_file:
173+
with open(TMP_OUTPUT_FILE, "r") as output_file:
172174
output_result = output_file.read()
173175
result: dict = {
174176
"exit_code": e.returncode,
175177
"stdout": e.stdout,
176178
"output": output_result,
177179
"exit_codes_explained": self.exit_code_explained,
178180
}
179-
os.remove("sysdig_cli_scanner_output.json")
181+
os.remove(TMP_OUTPUT_FILE)
180182
return result
181183
# Handle any other exceptions that may occur and exit codes 2 and 3
182184
except Exception as e:

tools/events_feed/tool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ def tool_list_runtime_events(
111111
Returns:
112112
dict: A dictionary containing the results of the runtime events query, including pagination information.
113113
"""
114+
start_time = time.time()
114115
api_instances: dict = ctx.get_state("api_instances")
115116
secure_events_api: SecureEventsApi = api_instances.get("secure_events")
116117

117-
start_time = time.time()
118118
# Compute time window
119119
now_ns = time.time_ns()
120120
from_ts = now_ns - scope_hours * 3600 * 1_000_000_000
@@ -160,10 +160,10 @@ def tool_get_event_process_tree(self, ctx: Context, event_id: str) -> dict:
160160
ToolError: If there is an error constructing or processing the response.
161161
"""
162162
try:
163+
start_time = time.time()
163164
api_instances: dict = ctx.get_state("api_instances")
164165
legacy_api_client: LegacySysdigApi = api_instances.get("legacy_sysdig_api")
165166

166-
start_time = time.time()
167167
# Get process tree branches
168168
branches = legacy_api_client.request_process_tree_branches(event_id)
169169
# Get process tree
@@ -194,7 +194,7 @@ def tool_get_event_process_tree(self, ctx: Context, event_id: str) -> dict:
194194
"metadata": {
195195
"execution_time_ms": (time.time() - start_time) * 1000,
196196
"timestamp": datetime.datetime.now(datetime.UTC).isoformat().replace("+00:00", "Z"),
197-
"note": "Process tree not available for this event"
197+
"note": "Process tree not available for this event",
198198
},
199199
}
200200
else:

tools/inventory/tool.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class InventoryTools:
1919
A class to encapsulate the tools for interacting with the Sysdig Secure Inventory API.
2020
This class provides methods to list resources and retrieve a single resource by its hash.
2121
"""
22+
2223
def __init__(self, app_config: AppConfig):
2324
self.app_config = app_config
2425
# Configure logging
@@ -35,7 +36,7 @@ def tool_list_resources(
3536
Sysdig Secure query filter expression to filter inventory resources.
3637
3738
Use the resource://filter-query-language to get the expected filter expression format.
38-
39+
3940
List of supported fields:
4041
- accountName
4142
- accountId
@@ -138,11 +139,10 @@ def tool_list_resources(
138139
Or a dict containing an error message if the call fails.
139140
"""
140141
try:
142+
start_time = time.time()
141143
api_instances: dict = ctx.get_state("api_instances")
142144
inventory_api: InventoryApi = api_instances.get("inventory")
143145

144-
start_time = time.time()
145-
146146
api_response = inventory_api.get_resources_without_preload_content(
147147
filter=filter_exp, page_number=page_number, page_size=page_size, with_enriched_containers=with_enrich_containers
148148
)
@@ -172,11 +172,10 @@ def tool_get_resource(
172172
dict: A dictionary containing the details of the requested inventory resource.
173173
"""
174174
try:
175+
start_time = time.time()
175176
api_instances: dict = ctx.get_state("api_instances")
176177
inventory_api: InventoryApi = api_instances.get("inventory")
177178

178-
start_time = time.time()
179-
180179
api_response = inventory_api.get_resource_without_preload_content(hash=resource_hash)
181180
execution_time = (time.time() - start_time) * 1000
182181

tools/sysdig_sage/tool.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class SageTools:
2020
This class provides methods to generate SysQL queries based on natural
2121
language questions and execute them against the Sysdig API.
2222
"""
23+
2324
def __init__(self, app_config: AppConfig):
2425
self.app_config = app_config
2526
self.log = logging.getLogger(__name__)

0 commit comments

Comments
 (0)