mcp-souschef 2.8.0__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,274 @@
1
+ """Validation Reports Page for SousChef UI."""
2
+
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import streamlit as st
9
+
10
+
11
+ def _run_ansible_lint(playbook_path: str) -> tuple[bool, str]:
12
+ """
13
+ Run ansible-lint on a playbook and return results.
14
+
15
+ Args:
16
+ playbook_path: Path to the playbook file.
17
+
18
+ Returns:
19
+ Tuple of (success: bool, output: str).
20
+
21
+ """
22
+ try:
23
+ result = subprocess.run(
24
+ [sys.executable, "-m", "ansible_lint", str(playbook_path)],
25
+ capture_output=True,
26
+ text=True,
27
+ timeout=30,
28
+ )
29
+ return result.returncode == 0, result.stdout + result.stderr
30
+ except subprocess.TimeoutExpired:
31
+ return False, "Validation timeout after 30 seconds"
32
+ except Exception as e:
33
+ return False, f"Error running ansible-lint: {e}"
34
+
35
+
36
+ def _validate_playbooks_in_directory(
37
+ directory: str,
38
+ ) -> dict[str, tuple[bool, str]]:
39
+ """
40
+ Validate all playbooks in a directory.
41
+
42
+ Args:
43
+ directory: Path to directory containing playbook files.
44
+
45
+ Returns:
46
+ Dictionary mapping playbook names to (success, output) tuples.
47
+
48
+ """
49
+ playbook_dir = Path(directory)
50
+ results = {}
51
+
52
+ if not playbook_dir.exists():
53
+ return {"error": (False, f"Directory not found: {directory}")}
54
+
55
+ # Find all YAML files
56
+ playbook_files = list(playbook_dir.glob("*.yml")) + list(
57
+ playbook_dir.glob("*.yaml")
58
+ )
59
+
60
+ if not playbook_files:
61
+ return {"no_playbooks": (False, "No playbook files found in directory")}
62
+
63
+ for playbook_file in playbook_files:
64
+ success, output = _run_ansible_lint(str(playbook_file))
65
+ results[playbook_file.name] = (success, output)
66
+
67
+ return results
68
+
69
+
70
+ def _parse_ansible_lint_output(output: str) -> dict[str, Any]:
71
+ """
72
+ Parse ansible-lint output into structured format.
73
+
74
+ Args:
75
+ output: Raw ansible-lint output.
76
+
77
+ Returns:
78
+ Dictionary with parsed results (warnings, errors, etc.).
79
+
80
+ """
81
+ lines = output.strip().split("\n")
82
+ parsed: dict[str, Any] = {
83
+ "warnings": 0,
84
+ "errors": 0,
85
+ "info": 0,
86
+ "details": [],
87
+ }
88
+
89
+ for line in lines:
90
+ if "warning" in line.lower():
91
+ parsed["warnings"] = int(parsed["warnings"]) + 1
92
+ elif "error" in line.lower():
93
+ parsed["errors"] = int(parsed["errors"]) + 1
94
+ elif "info" in line.lower():
95
+ parsed["info"] = int(parsed["info"]) + 1
96
+
97
+ if line.strip():
98
+ parsed["details"].append(line)
99
+
100
+ return parsed
101
+
102
+
103
+ def show_validation_reports_page():
104
+ """Show the validation reports page."""
105
+ # Add back to dashboard button
106
+ col1, _ = st.columns([1, 4])
107
+ with col1:
108
+ if st.button(
109
+ "← Back to Dashboard",
110
+ help="Return to main dashboard",
111
+ key="back_to_dashboard_from_validation",
112
+ ):
113
+ st.session_state.current_page = "Dashboard"
114
+ st.rerun()
115
+
116
+ st.header("✅ Validation Reports")
117
+
118
+ # Check if we have converted playbooks to validate
119
+ if not hasattr(st.session_state, "converted_playbooks_path"):
120
+ st.info(
121
+ "No converted playbooks available for validation. "
122
+ "Please run a cookbook analysis first to generate Ansible playbooks."
123
+ )
124
+ return
125
+
126
+ converted_path = st.session_state.converted_playbooks_path
127
+ playbook_path = Path(converted_path)
128
+
129
+ if not playbook_path.exists():
130
+ st.warning(f"Converted playbooks directory not found: {converted_path}")
131
+ return
132
+
133
+ st.subheader("Running Ansible Validation")
134
+ st.markdown("Validating converted Ansible playbooks using ansible-lint...")
135
+
136
+ # Run validation
137
+ with st.spinner("Validating playbooks..."):
138
+ results = _validate_playbooks_in_directory(converted_path)
139
+
140
+ if not results:
141
+ st.warning("No playbooks found to validate")
142
+ return
143
+
144
+ # Display summary
145
+ total_playbooks = len(results)
146
+ passed_count = sum(1 for success, _ in results.values() if success)
147
+ failed_count = total_playbooks - passed_count
148
+
149
+ col1, col2, col3 = st.columns(3)
150
+ with col1:
151
+ st.metric("Total Playbooks", total_playbooks)
152
+ with col2:
153
+ st.metric("Passed Validation", passed_count, delta=f"✓ {passed_count}")
154
+ with col3:
155
+ st.metric(
156
+ "Failed Validation",
157
+ failed_count,
158
+ delta=f"✗ {failed_count}" if failed_count > 0 else "✓ None",
159
+ )
160
+
161
+ # Display detailed results
162
+ st.subheader("Detailed Validation Results")
163
+
164
+ if passed_count == total_playbooks:
165
+ st.success("✅ All playbooks passed validation!")
166
+ else:
167
+ st.warning(f"⚠️ {failed_count} playbook(s) have validation issues")
168
+
169
+ # Create tabs for each playbook
170
+ if len(results) > 1:
171
+ tabs = st.tabs(list(results.keys()))
172
+ for tab, (playbook_name, (success, output)) in zip(
173
+ tabs, results.items(), strict=True
174
+ ):
175
+ with tab:
176
+ _display_validation_result(playbook_name, success, output)
177
+ else:
178
+ # Single playbook, no tabs needed
179
+ for playbook_name, (success, output) in results.items():
180
+ _display_validation_result(playbook_name, success, output)
181
+
182
+ # Export validation report
183
+ st.subheader("Export Report")
184
+ if st.button("📥 Download Validation Report"):
185
+ report = _generate_validation_report(results)
186
+ st.download_button(
187
+ label="Download as Text",
188
+ data=report,
189
+ file_name="validation_report.txt",
190
+ mime="text/plain",
191
+ )
192
+
193
+
194
+ def _display_validation_result(playbook_name: str, success: bool, output: str) -> None:
195
+ """
196
+ Display validation result for a single playbook.
197
+
198
+ Args:
199
+ playbook_name: Name of the playbook.
200
+ success: Whether validation passed.
201
+ output: Validation output/errors.
202
+
203
+ """
204
+ if success:
205
+ st.success(f"✅ {playbook_name} passed validation")
206
+ else:
207
+ st.error(f"❌ {playbook_name} failed validation")
208
+
209
+ with st.expander("View Details"):
210
+ if output:
211
+ st.code(output, language="text")
212
+ else:
213
+ st.info("No detailed output available")
214
+
215
+
216
+ def _generate_validation_report(results: dict[str, tuple[bool, str]]) -> str:
217
+ """
218
+ Generate a text validation report.
219
+
220
+ Args:
221
+ results: Dictionary of validation results.
222
+
223
+ Returns:
224
+ Formatted validation report as string.
225
+
226
+ """
227
+ report_lines = [
228
+ "=" * 80,
229
+ "ANSIBLE PLAYBOOK VALIDATION REPORT",
230
+ "=" * 80,
231
+ "",
232
+ ]
233
+
234
+ # Summary
235
+ total = len(results)
236
+ passed = sum(1 for success, _ in results.values() if success)
237
+ failed = total - passed
238
+
239
+ report_lines.extend(
240
+ [
241
+ "SUMMARY",
242
+ "-" * 80,
243
+ f"Total Playbooks: {total}",
244
+ f"Passed: {passed}",
245
+ f"Failed: {failed}",
246
+ "",
247
+ ]
248
+ )
249
+
250
+ # Detailed results
251
+ report_lines.extend(
252
+ [
253
+ "DETAILED RESULTS",
254
+ "-" * 80,
255
+ "",
256
+ ]
257
+ )
258
+
259
+ for playbook_name, (success, output) in results.items():
260
+ status = "PASSED" if success else "FAILED"
261
+ report_lines.extend(
262
+ [
263
+ f"Playbook: {playbook_name}",
264
+ f"Status: {status}",
265
+ "",
266
+ "Output:",
267
+ output if output else "(No output)",
268
+ "",
269
+ "-" * 80,
270
+ "",
271
+ ]
272
+ )
273
+
274
+ return "\n".join(report_lines)