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.
- {mcp_souschef-2.5.3.dist-info → mcp_souschef-3.0.0.dist-info}/METADATA +135 -28
- mcp_souschef-3.0.0.dist-info/RECORD +46 -0
- {mcp_souschef-2.5.3.dist-info → mcp_souschef-3.0.0.dist-info}/WHEEL +1 -1
- souschef/__init__.py +43 -3
- souschef/assessment.py +1260 -69
- souschef/ci/common.py +126 -0
- souschef/ci/github_actions.py +4 -93
- souschef/ci/gitlab_ci.py +3 -53
- souschef/ci/jenkins_pipeline.py +3 -60
- souschef/cli.py +129 -20
- souschef/converters/__init__.py +2 -2
- souschef/converters/cookbook_specific.py +125 -0
- souschef/converters/cookbook_specific.py.backup +109 -0
- souschef/converters/playbook.py +1022 -15
- souschef/converters/resource.py +113 -10
- souschef/converters/template.py +177 -0
- souschef/core/constants.py +13 -0
- souschef/core/metrics.py +313 -0
- souschef/core/path_utils.py +12 -9
- souschef/core/validation.py +53 -0
- souschef/deployment.py +85 -33
- souschef/parsers/attributes.py +397 -32
- souschef/parsers/recipe.py +48 -10
- souschef/server.py +715 -37
- souschef/ui/app.py +1658 -379
- souschef/ui/health_check.py +36 -0
- souschef/ui/pages/ai_settings.py +563 -0
- souschef/ui/pages/cookbook_analysis.py +3270 -166
- souschef/ui/pages/validation_reports.py +274 -0
- mcp_souschef-2.5.3.dist-info/RECORD +0 -38
- {mcp_souschef-2.5.3.dist-info → mcp_souschef-3.0.0.dist-info}/entry_points.txt +0 -0
- {mcp_souschef-2.5.3.dist-info → mcp_souschef-3.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|