mcp-souschef 2.8.0__py3-none-any.whl → 3.2.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.
Files changed (36) hide show
  1. {mcp_souschef-2.8.0.dist-info → mcp_souschef-3.2.0.dist-info}/METADATA +159 -384
  2. mcp_souschef-3.2.0.dist-info/RECORD +47 -0
  3. {mcp_souschef-2.8.0.dist-info → mcp_souschef-3.2.0.dist-info}/WHEEL +1 -1
  4. souschef/__init__.py +31 -7
  5. souschef/assessment.py +1451 -105
  6. souschef/ci/common.py +126 -0
  7. souschef/ci/github_actions.py +3 -92
  8. souschef/ci/gitlab_ci.py +2 -52
  9. souschef/ci/jenkins_pipeline.py +2 -59
  10. souschef/cli.py +149 -16
  11. souschef/converters/playbook.py +378 -138
  12. souschef/converters/resource.py +12 -11
  13. souschef/converters/template.py +177 -0
  14. souschef/core/__init__.py +6 -1
  15. souschef/core/metrics.py +313 -0
  16. souschef/core/path_utils.py +233 -19
  17. souschef/core/validation.py +53 -0
  18. souschef/deployment.py +71 -12
  19. souschef/generators/__init__.py +13 -0
  20. souschef/generators/repo.py +695 -0
  21. souschef/parsers/attributes.py +1 -1
  22. souschef/parsers/habitat.py +1 -1
  23. souschef/parsers/inspec.py +25 -2
  24. souschef/parsers/metadata.py +5 -3
  25. souschef/parsers/recipe.py +1 -1
  26. souschef/parsers/resource.py +1 -1
  27. souschef/parsers/template.py +1 -1
  28. souschef/server.py +1039 -121
  29. souschef/ui/app.py +486 -374
  30. souschef/ui/pages/ai_settings.py +74 -8
  31. souschef/ui/pages/cookbook_analysis.py +3216 -373
  32. souschef/ui/pages/validation_reports.py +274 -0
  33. mcp_souschef-2.8.0.dist-info/RECORD +0 -42
  34. souschef/converters/cookbook_specific.py.backup +0 -109
  35. {mcp_souschef-2.8.0.dist-info → mcp_souschef-3.2.0.dist-info}/entry_points.txt +0 -0
  36. {mcp_souschef-2.8.0.dist-info → mcp_souschef-3.2.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,42 +0,0 @@
1
- souschef/__init__.py,sha256=oJgStDVoLf97poc0CoNXfD6ZTRe23R9vE_37Q8L2OhE,650
2
- souschef/assessment.py,sha256=QRU3bgGrASYyp2f0aAgdnqt8t1Uvpz-0oj7Z5nuhMqw,53933
3
- souschef/ci/__init__.py,sha256=GHaqDk4auB2kI7_fQtF2FRg5nP-iX6wJchj_uTlXcYg,411
4
- souschef/ci/github_actions.py,sha256=-Ov7pD2cEyImMgOY3ZYBC7qTc6Z9Rq0WUUVCZ0HjBSE,11215
5
- souschef/ci/gitlab_ci.py,sha256=p7N5t5AavQyJMUFN1JwIBENltNggBBnpdQGkNbD6ieY,8373
6
- souschef/ci/jenkins_pipeline.py,sha256=rKeyc0fozKJRektAFz75J5rSODE5JpZXGwfvJs_v28w,9264
7
- souschef/cli.py,sha256=q7p9bY75PHFPAyHtXboWbMorxFoJ-ruojLS6JnzUrpw,33772
8
- souschef/converters/__init__.py,sha256=kkPm758XiFDdUYvdVPAVjssFvf15LasJcob1EY1EMIs,681
9
- souschef/converters/cookbook_specific.py,sha256=ZvE9-bZtyXuf2E3HW4E9_GWzAqtm7e4YHj9qm1Xu_XI,3767
10
- souschef/converters/cookbook_specific.py.backup,sha256=9XycZz6oNNTEtLINo0hHNR3df-pbZIkLprhLniE2ENI,3468
11
- souschef/converters/habitat.py,sha256=4eVGAcX0576zLXW9yqYvuaRxOK2g6BOIJo_ws-PonHU,22516
12
- souschef/converters/playbook.py,sha256=fHOBNwO2a1-_mFwdJpME3xQzgrtfvqrmfLcZOoChDIo,84425
13
- souschef/converters/resource.py,sha256=MTfesZ9aGj1GeYZXxoyeupFS6ErcxiZg81CQ1zK7FiE,13428
14
- souschef/core/__init__.py,sha256=80a0G6wQGfh-Z7U1gsFaeC97bM4sp_3YGil-lN02Pl0,1894
15
- souschef/core/constants.py,sha256=AyIpc9cJjQEDc1yOcoKc_2-cJB4PekSpPKEK2S9jDl8,5122
16
- souschef/core/errors.py,sha256=zj_LHDRxUvHmcs-nZjOWu4B_MhY5WVq2okyZOxyycCI,8605
17
- souschef/core/path_utils.py,sha256=2hG6joupZR_08MMpHHbv2KKOFu-8tMafuQMqV0_1Nqw,1790
18
- souschef/core/ruby_utils.py,sha256=vUeFZBdjbFFFJlwo4fIboR23rXmbcYL2Tztt4RYhps0,1043
19
- souschef/core/validation.py,sha256=f37CFiJwPVaC0tN1nKlLFHIAdcZwT2CPNPnj2SHavss,17896
20
- souschef/deployment.py,sha256=EANWsnMN0VxGS17gQdJU4yXhwmCzMe73TVXcRfQ7Eas,60332
21
- souschef/filesystem/__init__.py,sha256=2H7Pdeedz0MfmgpRlmii_vx61qr0jtxFndT0APPm6Qs,142
22
- souschef/filesystem/operations.py,sha256=OMMozBfV_o70b47KioiZ2i6HViiUQPE5mVBeKcKFepo,1654
23
- souschef/parsers/__init__.py,sha256=gF-vPslzs9iLxsaDzFnWDPpYFDAIwyAifjUwzYurPLg,1104
24
- souschef/parsers/attributes.py,sha256=JmgFaYjlT0i2__hCk6hSqZRjq4xJaTdGsBDgHEu3ItY,18239
25
- souschef/parsers/habitat.py,sha256=CtVgLfmpo7SLp-ADyR-DXRYaRUur3bwJjlnpLyocMhc,10483
26
- souschef/parsers/inspec.py,sha256=zhlIDZmE6A9qws5gnrLxhGGBfHOebNTLWpZZI-XXTkU,34209
27
- souschef/parsers/metadata.py,sha256=laSxlsebsgTzRDM_B8eIXddeApbR4ATm-W4GAGucZpc,6324
28
- souschef/parsers/recipe.py,sha256=guhEttS_AyT2kckbde3_-kAyGoR4JyFMNZFAP5V1XIM,7090
29
- souschef/parsers/resource.py,sha256=EHxmojbQ7Hx5GsL5-x9mFBYk3FhGTpx-qw1uUVKQkRo,5348
30
- souschef/parsers/template.py,sha256=iOHMoQH7KkPzigTyyoxav8rb2ENYmfxgcvKGqvoYkgU,10532
31
- souschef/profiling.py,sha256=a6Pn57CZR3UPR1YIY8gfbuTPRuPkFrfIFBmctOcaZgY,17315
32
- souschef/server.py,sha256=O5WAYc992M9QzowcURDsw91q-WUnErDL68ftjMLRuXo,87150
33
- souschef/ui/__init__.py,sha256=U3W6X4ww7CxQVuetcocObWn5iY6lUFiNX7WoDBJeD34,195
34
- souschef/ui/app.py,sha256=pKzoVQE_qy4FzjLIxF5BUpfe76evxz6a6Ossarib37Q,98112
35
- souschef/ui/health_check.py,sha256=mEzec8L6XPRekOBAXngqQz5dnzal-a4p3D70mkPriiA,903
36
- souschef/ui/pages/ai_settings.py,sha256=HRmTQjs0EPg8FdSkcozonpDDXnTf0WHn9IvFAhigGM4,15911
37
- souschef/ui/pages/cookbook_analysis.py,sha256=Llx3o1QJsD9PCHhZD_SjjVyz_JwrA9K3iToMtp9ZF9g,47108
38
- mcp_souschef-2.8.0.dist-info/METADATA,sha256=yc21kT6owG-dLD6pXdkEJOzpmQuSj7XS0QcLnyKZETo,51221
39
- mcp_souschef-2.8.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
40
- mcp_souschef-2.8.0.dist-info/entry_points.txt,sha256=NVSk61tLG4W0xEkWGOMXAVCIUyodyZCY_j3Z_0m6rkQ,80
41
- mcp_souschef-2.8.0.dist-info/licenses/LICENSE,sha256=t31dYSuvYYNw6trj-coWSsLK-Tg_Iyl8ObcolQcrUKM,1078
42
- mcp_souschef-2.8.0.dist-info/RECORD,,
@@ -1,109 +0,0 @@
1
- """Cookbook-specific resource conversions for common infrastructure cookbooks."""
2
-
3
- from typing import Any
4
-
5
- # Cookbook-specific include_recipe mappings
6
- # Maps cookbook names to their primary package installation parameters
7
- COOKBOOK_PACKAGE_MAPPINGS: dict[str, dict[str, Any]] = {
8
- "nodejs": {
9
- "module": "ansible.builtin.apt",
10
- "params": {"name": ["nodejs", "npm"], "state": "present", "update_cache": True}
11
- },
12
- "apache2": {
13
- "module": "ansible.builtin.apt",
14
- "params": {"name": "apache2", "state": "present", "update_cache": True}
15
- },
16
- "mysql": {
17
- "module": "ansible.builtin.apt",
18
- "params": {"name": "mysql-server", "state": "present", "update_cache": True}
19
- },
20
- "docker": {
21
- "module": "ansible.builtin.apt",
22
- "params": {
23
- "name": ["docker-ce", "docker-ce-cli", "containerd.io"],
24
- "state": "present",
25
- "update_cache": True
26
- }
27
- },
28
- }
29
-
30
-
31
- def get_cookbook_package_config(cookbook_name: str) -> dict[str, Any] | None:
32
- """
33
- Get package installation configuration for a specific cookbook.
34
-
35
- Args:
36
- cookbook_name: Name of the cookbook (e.g., 'nodejs', 'apache2').
37
-
38
- Returns:
39
- Dictionary with 'module' and 'params' keys, or None if not found.
40
- """
41
- return COOKBOOK_PACKAGE_MAPPINGS.get(cookbook_name)
42
-
43
-
44
- # Cookbook-specific resource type mappings
45
- # Maps resource types that are specific to certain cookbooks
46
- COOKBOOK_RESOURCE_MAPPINGS: dict[str, dict[str, Any]] = {
47
- "nodejs_npm": {
48
- "description": "Node.js npm package installation",
49
- "params_builder": "_build_nodejs_npm_params",
50
- },
51
- }
52
-
53
-
54
- def _build_nodejs_npm_params(
55
- resource_name: str, action: str, props: dict[str, Any]
56
- ) -> dict[str, Any]:
57
- """Build parameters for nodejs_npm resources."""
58
- from souschef.converters.resource import _normalize_template_value
59
-
60
- params = {"name": resource_name, "global": True}
61
- if "version" in props:
62
- params["version"] = _normalize_template_value(props["version"])
63
- if action == "install":
64
- params["state"] = "present"
65
- elif action == "remove":
66
- params["state"] = "absent"
67
- else:
68
- params["state"] = "present"
69
- return params
70
-
71
-
72
- def get_cookbook_resource_config(resource_type: str) -> dict[str, Any] | None:
73
- """
74
- Get configuration for cookbook-specific resource types.
75
-
76
- Args:
77
- resource_type: The resource type (e.g., 'nodejs_npm').
78
-
79
- Returns:
80
- Dictionary with resource configuration, or None if not found.
81
- """
82
- return COOKBOOK_RESOURCE_MAPPINGS.get(resource_type)
83
-
84
-
85
- def build_cookbook_resource_params(
86
- resource_type: str, resource_name: str, action: str, props: dict[str, Any]
87
- ) -> dict[str, Any] | None:
88
- """
89
- Build parameters for cookbook-specific resource types.
90
-
91
- Args:
92
- resource_type: The resource type.
93
- resource_name: The resource name.
94
- action: The Chef action.
95
- props: Parsed properties dictionary.
96
-
97
- Returns:
98
- Dictionary of Ansible module parameters, or None if not supported.
99
- """
100
- config = get_cookbook_resource_config(resource_type)
101
- if not config:
102
- return None
103
-
104
- builder_name = config.get("params_builder")
105
- if builder_name == "_build_nodejs_npm_params":
106
- return _build_nodejs_npm_params(resource_name, action, props)
107
-
108
- return None</content>
109
- <parameter name="filePath">/workspaces/souschef/souschef/converters/cookbook_specific.py