Skip to content

Commit f13bfa0

Browse files
fix: building json file in each job
1 parent f116a51 commit f13bfa0

8 files changed

Lines changed: 493 additions & 48 deletions

File tree

‎local_hooks/app_tests/json_tests.py‎

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from packaging.version import Version
2323
from pathlib import Path
2424

25+
SKIP_SDK_APP = {"success": True, "message": "TEST SKIPPED - SDK app detected"}
26+
2527

2628
class JSONTests(TestSuite):
2729
def __init__(self, repo_location: Path) -> None:
@@ -30,12 +32,6 @@ def __init__(self, repo_location: Path) -> None:
3032

3133
self._app_is_certified = self._parser.app_json["publisher"] == "Splunk"
3234

33-
def _skip_if_sdk_app(self):
34-
"""Helper method to skip tests for SDK apps"""
35-
if self._parser.uv_lock_file is not None:
36-
return {"success": True, "message": "TEST SKIPPED - SDK app detected"}
37-
return None
38-
3935
@staticmethod
4036
def format_as_index(indices: list[Any], container: str = "schema") -> str:
4137
"""
@@ -64,8 +60,8 @@ def validate_json_schema(self):
6460
"""
6561
Validates the structure of the app json
6662
"""
67-
if skip_result := self._skip_if_sdk_app():
68-
return skip_result
63+
if self._parser.uv_lovk_filepath:
64+
return SKIP_SDK_APP
6965

7066
APP_TESTS_DIR = Path(__file__).parent.resolve()
7167
schema_path = APP_TESTS_DIR / "app_schema.json"
@@ -97,8 +93,8 @@ def check_extra_fields_app_json(self):
9793
"""
9894
Makes sure certain fields are not in the app json
9995
"""
100-
if skip_result := self._skip_if_sdk_app():
101-
return skip_result
96+
if self._parser.uv_lovk_filepath:
97+
return SKIP_SDK_APP
10298

10399
to_remove = []
104100
msg = TEST_PASS_MESSAGE
@@ -123,8 +119,8 @@ def check_sequential_order(self):
123119
are sequential and zero-indexed
124120
"""
125121

126-
if skip_result := self._skip_if_sdk_app():
127-
return skip_result
122+
if self._parser.uv_lovk_filepath:
123+
return SKIP_SDK_APP
128124

129125
def check_sequence(order_tuples):
130126
try:
@@ -190,8 +186,8 @@ def check_main_module(self):
190186
"""
191187
Verifies that the main module field of the json is a valid connector filename
192188
"""
193-
if skip_result := self._skip_if_sdk_app():
194-
return skip_result
189+
if self._parser.uv_lovk_filepath:
190+
return SKIP_SDK_APP
195191

196192
app_json = self._app_json
197193
verbose = []
@@ -324,8 +320,8 @@ def check_action_param_prefixes(self):
324320
"""
325321
Every parameter has an action_result.parameter associated with it
326322
"""
327-
if skip_result := self._skip_if_sdk_app():
328-
return skip_result
323+
if self._parser.uv_lovk_filepath:
324+
return SKIP_SDK_APP
329325

330326
app_json_actions = self._app_json.get("actions")
331327

@@ -378,8 +374,8 @@ def check_action_param_matching_contains(self):
378374
"""
379375
Every parameter for an action with contains has an action_result.parameter with the same contains
380376
"""
381-
if skip_result := self._skip_if_sdk_app():
382-
return skip_result
377+
if self._parser.uv_lovk_filepath:
378+
return SKIP_SDK_APP
383379

384380
action_list = [act for act in self._app_json["actions"]]
385381
verbose = []
@@ -422,8 +418,8 @@ def check_minimal_data_paths(self):
422418
"""
423419
Checks to make sure each action includes the minimal required data paths
424420
"""
425-
if skip_result := self._skip_if_sdk_app():
426-
return skip_result
421+
if self._parser.uv_lovk_filepath:
422+
return SKIP_SDK_APP
427423

428424
app_json_actions = [act for act in self._app_json["actions"]]
429425
verbose = []
@@ -534,8 +530,8 @@ def check_pip313_dependencies(self):
534530
"""
535531
Ensures pip313_dependencies key exists and has appropriate content based on requirements.txt
536532
"""
537-
if skip_result := self._skip_if_sdk_app():
538-
return skip_result
533+
if self._parser.uv_lovk_filepath:
534+
return SKIP_SDK_APP
539535

540536
verbose = []
541537
message = TEST_PASS_MESSAGE

‎local_hooks/app_tests/utils/app_parser.py‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
from functools import cached_property
1010
from pathlib import Path
1111
from typing import Optional
12+
from local_hooks.helpers import (
13+
find_uv_lock_file,
14+
load_sdk_apps_enviornment,
15+
generate_sdk_app_manifest,
16+
)
1217

1318

1419
class ParserError(Exception):
@@ -103,16 +108,24 @@ def find_app_json_name(self, json_filenames):
103108

104109
@property
105110
def _app_json_filepath(self) -> Path:
106-
sdk_app_path = self._app_code_dir / "sdk_app_manifest.json"
107-
if sdk_app_path.exists():
108-
return sdk_app_path
109-
110111
return self._app_code_dir / self.app_json_name
111112

113+
@cached_property
114+
def sdk_app_json(self) -> Path:
115+
uv_lock_dir = self.uv_lovk_filepath.parent
116+
load_sdk_apps_enviornment(uv_lock_dir)
117+
return generate_sdk_app_manifest(uv_lock_dir)
118+
119+
@cached_property
120+
def uv_lovk_filepath(self) -> Optional[Path]:
121+
return find_uv_lock_file(self._app_code_dir)
122+
112123
@cached_property
113124
def app_json(self):
114125
# Gets the loaded json, preserving key order
115126
try:
127+
if self.uv_lovk_filepath:
128+
return self.sdk_app_json
116129
json_content = json.loads(self._app_json_filepath.read_text())
117130
return json_content
118131
except FileNotFoundError as e:

‎local_hooks/build_docs.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
from jinja2 import Environment, FileSystemLoader, select_autoescape
2222
from jinja2.ext import Extension
2323
from jinja2.lexer import Token, TokenStream
24+
from local_hooks.helpers import (
25+
find_uv_lock_file,
26+
load_sdk_apps_enviornment,
27+
generate_sdk_app_manifest,
28+
)
2429

2530
SCRIPT_DIR = Path(__file__).parent.resolve()
2631
TEMPLATE_DIR = SCRIPT_DIR / "templates"
@@ -88,6 +93,20 @@ def escape_markdown(data: Any) -> Any:
8893

8994
def get_app_json(app_json_dir, json_name):
9095
logging.info("Looking for app JSON in: %s", app_json_dir)
96+
97+
# Check if this is an SDK app and generate manifest if needed
98+
if uv_lock_path := find_uv_lock_file(app_json_dir):
99+
uv_lock_dir = uv_lock_path.parent
100+
logging.info("SDK app detected, generating manifest: UV_LOCK_DIR=%s", uv_lock_dir)
101+
102+
# Install dependencies from pyproject.toml using uv
103+
load_sdk_apps_enviornment(uv_lock_dir)
104+
105+
# Generate the SDK manifest in a temporary directory
106+
logging.info("Generating SDK app manifest in temporary directory")
107+
return generate_sdk_app_manifest(uv_lock_dir)
108+
109+
# Handle non-SDK apps - file-based JSON loading
91110
if not json_name:
92111
try:
93112
app_json_name = next(
@@ -210,6 +229,7 @@ def parse_args() -> BuildDocsArgs:
210229
help_str = " ".join(line.strip() for line in (__doc__ or "").strip().splitlines())
211230
parser = argparse.ArgumentParser(description=help_str)
212231
parser.add_argument("connector_path", help="Path to the connector", type=Path)
232+
# prob have to build the manifest here instead of in a seperate hook
213233
parser.add_argument("--json-name", help="JSON file name")
214234
return BuildDocsArgs(**vars(parser.parse_args()))
215235

‎local_hooks/generate_notice.py‎

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import logging
1414
from pathlib import Path
1515
import toml
16+
from local_hooks.helpers import find_uv_lock_file
1617

1718

1819
# pip-licenses is used to query all python packages for license information
@@ -164,10 +165,18 @@ def get_python_license_info(packages: list[str]):
164165
yield LicenseLine.make_from_pip_json(license_info)
165166

166167

167-
def get_app_json(connector_path: Path) -> tuple[str, str]:
168+
def get_app_json(connector_path: Path, uv_lock_path: Optional[Path]) -> tuple[str, str]:
168169
"""
169170
Get the app name and license from the app.json file.
170171
"""
172+
173+
if uv_lock_path:
174+
with open(uv_lock_path / "pyproject.toml") as f:
175+
toml_data = toml.load(f)
176+
app_name = toml_data.get("project", {}).get("name")
177+
app_license = toml_data.get("project", {}).get("license")
178+
return app_name, app_license
179+
171180
logging.info("Looking for app JSON in: %s", connector_path)
172181
json_files = glob.glob(os.path.join(connector_path, "*.json"))
173182
# Exclude files with the pattern '*.postman_collection.json'
@@ -226,25 +235,6 @@ def remove_trailing_blank_lines(notice_file_path: Path):
226235
f.write("\n")
227236

228237

229-
def find_uv_lock_file(connector_path: Path) -> Optional[Path]:
230-
"""
231-
Find uv.lock file in the connector_path or its subdirectories.
232-
Returns the path to the uv.lock file if found, None otherwise.
233-
"""
234-
# Check top level directory first
235-
uv_lock_path = connector_path / "uv.lock"
236-
if uv_lock_path.exists():
237-
logging.info("Found uv.lock in top level directory: %s", uv_lock_path)
238-
return uv_lock_path
239-
240-
# Check subdirectories
241-
for uv_lock_path in connector_path.rglob("uv.lock"):
242-
logging.info("Found uv.lock in subdirectory: %s", uv_lock_path)
243-
return uv_lock_path
244-
245-
return None
246-
247-
248238
def get_sdkfied_app_dependencies(pyproject_toml_path: Path) -> list[str]:
249239
"""
250240
Get the dependencies from the pyproject.toml file.
@@ -265,7 +255,8 @@ def main():
265255
args = parse_args()
266256
connector_path = Path(args.connector_path)
267257

268-
app_name, app_license = get_app_json(connector_path)
258+
uv_lock_path = find_uv_lock_file(connector_path)
259+
app_name, app_license = get_app_json(connector_path, uv_lock_path.parent)
269260
notice_file_path = connector_path / "NOTICE"
270261
logging.info("Creating NOTICE file at %s", Path(notice_file_path.resolve()))
271262

@@ -274,7 +265,7 @@ def main():
274265
f.write(f"Splunk SOAR App: {app_name}\n{app_license}\n")
275266

276267
# Get all python package dependencies
277-
if uv_lock_path := find_uv_lock_file(connector_path):
268+
if uv_lock_path:
278269
uv_lock_dir = uv_lock_path.parent
279270
packages = get_sdkfied_app_dependencies(uv_lock_dir / "pyproject.toml")
280271
else:

‎local_hooks/helpers.py‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import logging
2+
from pathlib import Path
3+
from typing import Optional
4+
import subprocess
5+
import tempfile
6+
import json
7+
8+
9+
def find_uv_lock_file(connector_path: Path) -> Optional[Path]:
10+
"""
11+
Find uv.lock file in the connector_path or its subdirectories.
12+
Returns the path to the uv.lock file if found, None otherwise.
13+
"""
14+
# By convention, SDK apps are managed by uv and old apps are not
15+
for uv_lock_path in connector_path.rglob("uv.lock"):
16+
logging.info("Found uv.lock at: %s", uv_lock_path)
17+
return uv_lock_path
18+
19+
20+
def load_sdk_apps_enviornment(uv_lock_dir: Path):
21+
"""
22+
Load the SDK app's environment.
23+
"""
24+
pyproject_path = uv_lock_dir / "pyproject.toml"
25+
if pyproject_path.exists():
26+
logging.info("Installing dependencies from pyproject.toml")
27+
try:
28+
subprocess.run(
29+
["uv", "sync"], cwd=uv_lock_dir, check=True, capture_output=True, text=True
30+
)
31+
logging.info("Successfully installed dependencies")
32+
33+
except subprocess.CalledProcessError as e:
34+
logging.error("Failed to install dependencies: %s", e)
35+
raise e
36+
else:
37+
raise ValueError("No pyproject.toml found in %s", uv_lock_dir)
38+
39+
40+
def generate_sdk_app_manifest(uv_lock_dir: Path) -> dict:
41+
with tempfile.TemporaryDirectory() as temp_dir:
42+
temp_manifest_path = Path(temp_dir) / "sdk_app_manifest.json"
43+
try:
44+
# Use uv run to execute soarapps in the SDK app's environment
45+
subprocess.run(
46+
[
47+
"uv",
48+
"run",
49+
"soarapps",
50+
"manifests",
51+
"create",
52+
str(temp_manifest_path),
53+
".",
54+
],
55+
check=True,
56+
cwd=uv_lock_dir, # Run from the SDK app directory
57+
)
58+
logging.info("Successfully generated SDK manifest in temp directory")
59+
60+
with open(temp_manifest_path) as f:
61+
return json.load(f)
62+
63+
except subprocess.CalledProcessError as e:
64+
logging.error("Failed to generate SDK manifest: %s", e)
65+
raise e
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "test"
3+
version = "4.0.0"
4+
description = "This is a test app"
5+
license = "Copyright (c) 2025 Splunk Inc."
6+
requires-python = ">=3.9, <3.14"
7+
authors = [
8+
]
9+
dependencies = [
10+
"splunk-soar-sdk==1.6.0",
11+
"validators",
12+
"xmltodict",
13+
]

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies = [
1616
"requests>=2.32.3",
1717
"pip-licenses",
1818
"toml",
19+
"splunk-soar-sdk",
1920
]
2021

2122
[project.scripts]

0 commit comments

Comments
 (0)