diff --git a/README.md b/README.md index 3e3055d..dbd7daf 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - πŸ“¦ Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ namespaces, deployments, pods, ConfigMaps - πŸ”„ Быстрый рСстарт ΠΈ ΠΌΠ°ΡΡˆΡ‚Π°Π±ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ deployments - πŸ“ ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π»ΠΎΠ³ΠΎΠ² pods -- πŸ” ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ содСрТимого ConfigMaps +- πŸ” ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ ΠΈ Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ConfigMaps Π² тСкстовом Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΎΡ€Π΅ - πŸ’Ύ Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ настроСк Π² `~/.config/k8s-tool/k8s-tool.cfg` ## ВрСбования @@ -95,6 +95,14 @@ poetry run k8s-tool МоТно Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ ConfigMap для просмотра Π΅Π³ΠΎ содСрТимого с подсвСткой синтаксиса. +#### Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ConfigMap +ΠžΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΡƒΠ½ΠΊΡ‚ мСню для рСдактирования ConfigMaps: +- Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ ConfigMap ΠΈΠ· списка +- ΠžΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ΡΡ тСкстовый Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΎΡ€ (ΠΈΠ· ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ окруТСния `$EDITOR` ΠΈΠ»ΠΈ `$VISUAL`, ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ `vi`) +- Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΡƒΠΉΡ‚Π΅ YAML с Π΄Π°Π½Π½Ρ‹ΠΌΠΈ ConfigMap +- ПослС сохранСния показываСтся diff ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ (Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹Π΅, ΡƒΠ΄Π°Π»Π΅Π½Π½Ρ‹Π΅, ΠΈΠ·ΠΌΠ΅Π½Π΅Π½Π½Ρ‹Π΅ ΠΊΠ»ΡŽΡ‡ΠΈ) +- ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ ΠΏΠ΅Ρ€Π΅Π΄ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ΠΌ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π² кластСр + #### ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π»ΠΎΠ³ΠΎΠ² Pod Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ pod ΠΈ ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€ (Ссли ΠΈΡ… нСсколько) для просмотра Π»ΠΎΠ³ΠΎΠ². МоТно ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ количСство строк для отобраТСния (ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ 100). diff --git a/k8s_tool/k8s_client.py b/k8s_tool/k8s_client.py index 434f72a..0823859 100644 --- a/k8s_tool/k8s_client.py +++ b/k8s_tool/k8s_client.py @@ -89,6 +89,21 @@ class K8sClient: console.print(f"[red]Error reading ConfigMap:[/red] {e}") return None + def update_configmap(self, namespace: str, name: str, data: Dict[str, str]) -> bool: + """Update ConfigMap data.""" + try: + # Read the existing ConfigMap first + cm = self.v1.read_namespaced_config_map(name, namespace) + # Update only the data field + cm.data = data + # Patch the ConfigMap + self.v1.patch_namespaced_config_map(name, namespace, cm) + console.print(f"[green]βœ“[/green] ConfigMap {name} updated successfully") + return True + except ApiException as e: + console.print(f"[red]Error updating ConfigMap:[/red] {e}") + return False + def restart_deployment(self, namespace: str, name: str) -> bool: """Restart deployment by updating annotation.""" try: diff --git a/k8s_tool/main.py b/k8s_tool/main.py index 125e76d..17730f8 100644 --- a/k8s_tool/main.py +++ b/k8s_tool/main.py @@ -1,7 +1,11 @@ """Main application entry point.""" import sys -from typing import Optional +import os +import subprocess +import tempfile +import yaml +from typing import Optional, Dict import questionary from questionary import Style from rich.console import Console @@ -71,6 +75,7 @@ class K8sTool: "Restart Deployment", "Scale Deployment", "View ConfigMaps", + "Edit ConfigMap", "View Pod Logs", "Exit" ] @@ -93,6 +98,8 @@ class K8sTool: self._scale_deployment() elif action == "View ConfigMaps": self._view_configmaps() + elif action == "Edit ConfigMap": + self._edit_configmaps() elif action == "View Pod Logs": self._view_pod_logs() elif action == "Exit": @@ -386,6 +393,108 @@ class K8sTool: console.print(Syntax(value, "yaml", line_numbers=False, background_color="default")) console.print() + def _edit_configmaps(self): + """Edit a ConfigMap.""" + if not self._ensure_namespace_selected(): + return + + console.print(f"[dim]Fetching ConfigMaps in {self.current_namespace}...[/dim]") + configmaps = self.k8s_client.get_configmaps(self.current_namespace) + + if not configmaps: + console.print("[yellow]No ConfigMaps found[/yellow]") + return + + cm_names = [cm['name'] for cm in configmaps] + cm_name = questionary.select( + "Select ConfigMap to edit:", + choices=cm_names, + style=custom_style + ).ask() + + if cm_name: + data = self.k8s_client.get_configmap_data(self.current_namespace, cm_name) + if data: + self._edit_configmap(cm_name, data) + + def _edit_configmap(self, cm_name: str, current_data: Dict[str, str]): + """Edit ConfigMap data in text editor.""" + # Get editor from environment or use default + editor = os.environ.get('EDITOR', os.environ.get('VISUAL', 'vi')) + + # Create temporary file with ConfigMap data + with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as tf: + temp_file = tf.name + # Write current data as YAML + yaml.dump(current_data, tf, default_flow_style=False, allow_unicode=True) + + try: + # Get modification time before editing + mtime_before = os.path.getmtime(temp_file) + + # Open editor + subprocess.call([editor, temp_file]) + + # Check if file was modified + mtime_after = os.path.getmtime(temp_file) + + if mtime_after == mtime_before: + console.print("[yellow]No changes made[/yellow]") + return + + # Read edited data + with open(temp_file, 'r') as f: + try: + new_data = yaml.safe_load(f) + except yaml.YAMLError as e: + console.print(f"[red]Error parsing YAML:[/red] {e}") + if questionary.confirm("Retry editing?", style=custom_style, default=False).ask(): + self._edit_configmap(cm_name, current_data) + return + + # Validate that new_data is a dict + if not isinstance(new_data, dict): + console.print("[red]Error:[/red] ConfigMap data must be a dictionary") + if questionary.confirm("Retry editing?", style=custom_style, default=False).ask(): + self._edit_configmap(cm_name, current_data) + return + + # Convert all values to strings (ConfigMap requirement) + new_data = {k: str(v) if not isinstance(v, str) else v for k, v in new_data.items()} + + # Show diff + console.print("\n[bold]Changes:[/bold]") + removed_keys = set(current_data.keys()) - set(new_data.keys()) + added_keys = set(new_data.keys()) - set(current_data.keys()) + modified_keys = {k for k in current_data.keys() & new_data.keys() + if current_data[k] != new_data[k]} + + if removed_keys: + console.print(f"[red]Removed keys:[/red] {', '.join(removed_keys)}") + if added_keys: + console.print(f"[green]Added keys:[/green] {', '.join(added_keys)}") + if modified_keys: + console.print(f"[yellow]Modified keys:[/yellow] {', '.join(modified_keys)}") + + if not (removed_keys or added_keys or modified_keys): + console.print("[yellow]No changes detected[/yellow]") + return + + # Confirm update + if questionary.confirm( + f"Apply changes to ConfigMap '{cm_name}'?", + style=custom_style, + default=False + ).ask(): + self.k8s_client.update_configmap(self.current_namespace, cm_name, new_data) + + finally: + # Clean up temp file + try: + os.unlink(temp_file) + except Exception: + pass + def _view_pod_logs(self): """View pod logs.""" if not self._ensure_namespace_selected(): diff --git a/pyproject.toml b/pyproject.toml index debda16..3b42022 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,8 @@ requires-python = "^3.9" dependencies = [ "questionary (>=2.1.1,<3.0.0)", "kubernetes (>=34.1.0,<35.0.0)", - "rich (>=14.2.0,<15.0.0)" + "rich (>=14.2.0,<15.0.0)", + "pyyaml (>=6.0.3,<7.0.0)" ] [project.scripts]