Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ocp-admin/scripts/cluster-report/assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def unwrap_persisted_output(raw_content):


def resolve_file_ref(file_path):
abs_path = os.path.realpath(file_path)
if not abs_path.startswith("/tmp/"):
return None, f"Path outside allowed directory: {file_path}"

if not os.path.exists(file_path):
return None, f"File not found: {file_path}"

Expand Down
13 changes: 9 additions & 4 deletions ocp-admin/scripts/cluster-report/test_assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,15 @@ def test_valid_plain_json_file(self):
os.unlink(path)

def test_missing_file(self):
result, error = assemble.resolve_file_ref("/nonexistent/path/file.json")
result, error = assemble.resolve_file_ref("/tmp/nonexistent-file.json")
self.assertIsNone(result)
self.assertIn("not found", error.lower())

def test_path_outside_allowed_directory(self):
result, error = assemble.resolve_file_ref("/etc/passwd")
self.assertIsNone(result)
self.assertIn("outside allowed directory", error.lower())

def test_empty_file(self):
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
path = f.name
Expand Down Expand Up @@ -223,7 +228,7 @@ def test_file_ref_resolved(self):

def test_failed_file_ref_adds_error(self):
cluster = {
"pods": {"$file": "/nonexistent/file.json"},
"pods": {"$file": "/tmp/nonexistent-file.json"},
"nodes_top": None,
"nodes_list": None,
"projects": None,
Expand All @@ -237,7 +242,7 @@ def test_failed_file_ref_adds_error(self):

def test_preserves_existing_errors(self):
cluster = {
"pods": {"$file": "/nonexistent/file.json"},
"pods": {"$file": "/tmp/nonexistent-file.json"},
"nodes_top": None,
"nodes_list": None,
"projects": None,
Expand Down Expand Up @@ -400,7 +405,7 @@ def test_file_ref_error_in_pipeline(self):
"nodes_list": None,
"projects": None,
"namespaces": None,
"pods": {"$file": "/nonexistent/file.json"},
"pods": {"$file": "/tmp/nonexistent-file.json"},
"errors": [],
}
},
Expand Down
10 changes: 5 additions & 5 deletions ocp-admin/skills/cluster-report/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ This skill uses `openshift-administration` MCP server exclusively. This server p

**Multi-Cluster Setup**: For large-scale deployments using service account tokens instead of interactive `oc login`, see [multi-cluster-auth.md](../../docs/multi-cluster-auth.md) and the [build-kubeconfig.py](../../scripts/cluster-report/build-kubeconfig.py) helper script.

**Helper Scripts** (Python 3, stdlib only — treat as black boxes):
**Helper Scripts** (Python 3, stdlib only — auditable, do not reimplement):
- [`assemble.py`](../../scripts/cluster-report/assemble.py) — resolves `$file` references into complete raw data JSON
- [`aggregate.py`](../../scripts/cluster-report/aggregate.py) — aggregates raw data into structured report JSON

**CRITICAL Script Rules**:
- **NEVER** read the source code of `aggregate.py` or `assemble.py`
- **NEVER** write ad-hoc Python to parse or transform MCP output
- **NEVER** manually reconstruct data already available in MCP output
**Script Usage Rules**:
- Invoke scripts via the documented pipeline (Step 3) — do NOT reimplement their logic inline
- Do NOT write ad-hoc Python to parse or transform MCP output — the scripts handle all parsing
- You MAY read the scripts for debugging if the pipeline returns errors

**Verification**:
1. Check `openshift-administration` in `mcps.json`
Expand Down
Loading