mcp-souschef 2.5.3__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)
@@ -1,38 +0,0 @@
1
- souschef/__init__.py,sha256=Lkrvi2wu-OMRkdlwzxencm8QzBKc1FpK4y6SWhKqrqI,432
2
- souschef/assessment.py,sha256=UH9vX9GZYKXrRkavk_Gue1chT2u4Hb8fSd0jtMzwHgk,53933
3
- souschef/ci/__init__.py,sha256=GHaqDk4auB2kI7_fQtF2FRg5nP-iX6wJchj_uTlXcYg,411
4
- souschef/ci/github_actions.py,sha256=62bkuD3YCKRCK1wgyo-3l-hASyYsinZB3FOTRqWc7Ag,11215
5
- souschef/ci/gitlab_ci.py,sha256=jNyeeucg6TXS0l1BJHrPm_cNXe1XJEjWlaOLnQI3eZo,8373
6
- souschef/ci/jenkins_pipeline.py,sha256=TlwyfirlUx01TnuinFHBSZVFmnxmSEdqln_71f6lWhs,9264
7
- souschef/cli.py,sha256=DjVPJzzhIbbkf9l4vj68qA4aSBTetlwyiLSZeP3Ctsk,33772
8
- souschef/converters/__init__.py,sha256=WHBBPH60_rPX1LVieimqIYlVzeV8ttG4Q7N1dMUMBb0,681
9
- souschef/converters/habitat.py,sha256=4eVGAcX0576zLXW9yqYvuaRxOK2g6BOIJo_ws-PonHU,22516
10
- souschef/converters/playbook.py,sha256=i8HKxoJPLzw47rsq3pXreYSqte5_lBozgbTdyzMnIwA,57336
11
- souschef/converters/resource.py,sha256=9cuAeTxVwl7aejFxozW8nQXMvapnT1LKu77duuknZnc,9692
12
- souschef/core/__init__.py,sha256=80a0G6wQGfh-Z7U1gsFaeC97bM4sp_3YGil-lN02Pl0,1894
13
- souschef/core/constants.py,sha256=wJBKtDUpGUD02RZUT7RPclITJqkeJKe9ny6_G-qSCOw,4694
14
- souschef/core/errors.py,sha256=zj_LHDRxUvHmcs-nZjOWu4B_MhY5WVq2okyZOxyycCI,8605
15
- souschef/core/path_utils.py,sha256=zfhNDQtSgiO8nK_3-uOZuM28rn3AVuRZ81o-D9Mrt1E,1731
16
- souschef/core/ruby_utils.py,sha256=vUeFZBdjbFFFJlwo4fIboR23rXmbcYL2Tztt4RYhps0,1043
17
- souschef/core/validation.py,sha256=f37CFiJwPVaC0tN1nKlLFHIAdcZwT2CPNPnj2SHavss,17896
18
- souschef/deployment.py,sha256=5Ug-GS1bWXHOgPb3pzfdGJb3IZGDMl_LyGxY9vf-pDc,60332
19
- souschef/filesystem/__init__.py,sha256=2H7Pdeedz0MfmgpRlmii_vx61qr0jtxFndT0APPm6Qs,142
20
- souschef/filesystem/operations.py,sha256=OMMozBfV_o70b47KioiZ2i6HViiUQPE5mVBeKcKFepo,1654
21
- souschef/parsers/__init__.py,sha256=gF-vPslzs9iLxsaDzFnWDPpYFDAIwyAifjUwzYurPLg,1104
22
- souschef/parsers/attributes.py,sha256=JrxBYUOdE1Uu7c7mlqdjmyvZhXvuQ2VFzC19JpVUZ5U,7919
23
- souschef/parsers/habitat.py,sha256=CtVgLfmpo7SLp-ADyR-DXRYaRUur3bwJjlnpLyocMhc,10483
24
- souschef/parsers/inspec.py,sha256=zhlIDZmE6A9qws5gnrLxhGGBfHOebNTLWpZZI-XXTkU,34209
25
- souschef/parsers/metadata.py,sha256=laSxlsebsgTzRDM_B8eIXddeApbR4ATm-W4GAGucZpc,6324
26
- souschef/parsers/recipe.py,sha256=6PuslWMc0R8HlMaS8FaMKc0FtfuPfH4jVEP29QiCezQ,5941
27
- souschef/parsers/resource.py,sha256=EHxmojbQ7Hx5GsL5-x9mFBYk3FhGTpx-qw1uUVKQkRo,5348
28
- souschef/parsers/template.py,sha256=iOHMoQH7KkPzigTyyoxav8rb2ENYmfxgcvKGqvoYkgU,10532
29
- souschef/profiling.py,sha256=a6Pn57CZR3UPR1YIY8gfbuTPRuPkFrfIFBmctOcaZgY,17315
30
- souschef/server.py,sha256=J7RidGw-EuqWx7-Qm3e5IeLWaVVgq7KxAZicgJRMqAI,87252
31
- souschef/ui/__init__.py,sha256=U3W6X4ww7CxQVuetcocObWn5iY6lUFiNX7WoDBJeD34,195
32
- souschef/ui/app.py,sha256=qOBKFuoZJOzncMWKc4U4u6QdYdgU9i2LV89aLanlWjU,60325
33
- souschef/ui/pages/cookbook_analysis.py,sha256=VTL2dlH6AaXiSUycrzcqfCmN5xx9bJpsXK5MkrJFOGs,14014
34
- mcp_souschef-2.5.3.dist-info/METADATA,sha256=wqHtXXN6x9I0QO3r-cq4VeHMcU4pMCpmX1wrui85kHg,50244
35
- mcp_souschef-2.5.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
36
- mcp_souschef-2.5.3.dist-info/entry_points.txt,sha256=NVSk61tLG4W0xEkWGOMXAVCIUyodyZCY_j3Z_0m6rkQ,80
37
- mcp_souschef-2.5.3.dist-info/licenses/LICENSE,sha256=t31dYSuvYYNw6trj-coWSsLK-Tg_Iyl8ObcolQcrUKM,1078
38
- mcp_souschef-2.5.3.dist-info/RECORD,,