"""Main application entry point.""" import sys from typing import Optional import questionary from questionary import Style from rich.console import Console from rich.panel import Panel from rich.syntax import Syntax from k8s_tool.k8s_client import K8sClient from k8s_tool.config import ConfigManager console = Console() # Custom style for questionary prompts custom_style = Style([ ('qmark', 'fg:#673ab7 bold'), ('question', 'bold'), ('answer', 'fg:#f44336 bold'), ('pointer', 'fg:#673ab7 bold'), ('highlighted', 'fg:#673ab7 bold'), ('selected', 'fg:#cc5454'), ('separator', 'fg:#cc5454'), ('instruction', ''), ('text', ''), ]) class K8sTool: """Main application class.""" def __init__(self): """Initialize the application.""" self.k8s_client = K8sClient() self.config = ConfigManager() self.current_namespace: Optional[str] = None def run(self): """Run the main application loop.""" console.print(Panel.fit( "[bold cyan]K8s Tool[/bold cyan]\n" "[dim]Interactive kubectl helper[/dim]", border_style="cyan" )) while True: try: self._main_menu() except KeyboardInterrupt: console.print("\n[yellow]Goodbye! 👋[/yellow]") sys.exit(0) except Exception as e: console.print(f"[red]Error:[/red] {e}") if questionary.confirm("Continue?", style=custom_style).ask(): continue else: break def _main_menu(self): """Display main menu.""" namespace_info = f"[cyan]{self.current_namespace}[/cyan]" if self.current_namespace else "[dim]not selected[/dim]" is_favorite = self.config.is_favorite(self.current_namespace) if self.current_namespace else False fav_indicator = " ⭐" if is_favorite else "" console.print(f"\n[bold]Current namespace:[/bold] {namespace_info}{fav_indicator}") choices = [ "Select Namespace", "Manage Favorites", "List Deployments", "Restart Deployment", "Scale Deployment", "View ConfigMaps", "View Pod Logs", "Exit" ] action = questionary.select( "What would you like to do?", choices=choices, style=custom_style ).ask() if action == "Select Namespace": self._select_namespace() elif action == "Manage Favorites": self._manage_favorites() elif action == "List Deployments": self._list_deployments() elif action == "Restart Deployment": self._restart_deployment() elif action == "Scale Deployment": self._scale_deployment() elif action == "View ConfigMaps": self._view_configmaps() elif action == "View Pod Logs": self._view_pod_logs() elif action == "Exit": console.print("[yellow]Goodbye! 👋[/yellow]") sys.exit(0) def _select_namespace(self): """Select namespace.""" console.print("[dim]Fetching namespaces...[/dim]") namespaces = self.k8s_client.get_namespaces() if not namespaces: console.print("[red]No namespaces found[/red]") return # Get favorites favorites = self.config.get_favorites() # Separate favorites and non-favorites fav_namespaces = [ns for ns in namespaces if ns in favorites] other_namespaces = [ns for ns in namespaces if ns not in favorites] # Create choices with stars for favorites choices = [] if fav_namespaces: choices.extend([f"⭐ {ns}" for ns in fav_namespaces]) if other_namespaces: choices.append(questionary.Separator("─" * 40)) choices.extend(other_namespaces) selected = questionary.select( "Select namespace:", choices=choices, style=custom_style ).ask() if selected: # Remove star prefix if present namespace = selected.replace("⭐ ", "") self.current_namespace = namespace is_fav = self.config.is_favorite(namespace) fav_text = " (favorite)" if is_fav else "" console.print(f"[green]✓[/green] Namespace set to: [cyan]{namespace}[/cyan]{fav_text}") def _manage_favorites(self): """Manage favorite namespaces.""" choices = [] if self.current_namespace: if self.config.is_favorite(self.current_namespace): choices.append(f"Remove '{self.current_namespace}' from favorites") else: choices.append(f"Add '{self.current_namespace}' to favorites") choices.extend([ "View all favorites", "Add namespace to favorites", "Remove namespace from favorites", "Clear all favorites", "Back to main menu" ]) action = questionary.select( "Favorites management:", choices=choices, style=custom_style ).ask() if not action or action == "Back to main menu": return if action.startswith("Add '") and action.endswith("' to favorites"): # Add current namespace self.config.add_favorite(self.current_namespace) elif action.startswith("Remove '") and action.endswith("' from favorites"): # Remove current namespace self.config.remove_favorite(self.current_namespace) elif action == "View all favorites": self._view_favorites() elif action == "Add namespace to favorites": self._add_namespace_to_favorites() elif action == "Remove namespace from favorites": self._remove_namespace_from_favorites() elif action == "Clear all favorites": if questionary.confirm("Are you sure you want to clear all favorites?", style=custom_style, default=False).ask(): self.config.clear_favorites() def _view_favorites(self): """View all favorite namespaces.""" favorites = self.config.get_favorites() if not favorites: console.print("[yellow]No favorites yet[/yellow]") return console.print("\n[bold]Favorite namespaces:[/bold]") for ns in favorites: indicator = " [dim](current)[/dim]" if ns == self.current_namespace else "" console.print(f" ⭐ [cyan]{ns}[/cyan]{indicator}") console.print() def _add_namespace_to_favorites(self): """Add a namespace to favorites.""" console.print("[dim]Fetching namespaces...[/dim]") namespaces = self.k8s_client.get_namespaces() if not namespaces: console.print("[red]No namespaces found[/red]") return # Filter out already favorite namespaces favorites = self.config.get_favorites() available = [ns for ns in namespaces if ns not in favorites] if not available: console.print("[yellow]All namespaces are already in favorites[/yellow]") return namespace = questionary.select( "Select namespace to add to favorites:", choices=available, style=custom_style ).ask() if namespace: self.config.add_favorite(namespace) def _remove_namespace_from_favorites(self): """Remove a namespace from favorites.""" favorites = self.config.get_favorites() if not favorites: console.print("[yellow]No favorites to remove[/yellow]") return namespace = questionary.select( "Select namespace to remove from favorites:", choices=favorites, style=custom_style ).ask() if namespace: self.config.remove_favorite(namespace) def _ensure_namespace_selected(self) -> bool: """Ensure namespace is selected.""" if not self.current_namespace: console.print("[yellow]Please select a namespace first[/yellow]") self._select_namespace() return self.current_namespace is not None return True def _list_deployments(self): """List deployments in current namespace.""" if not self._ensure_namespace_selected(): return console.print(f"[dim]Fetching deployments in {self.current_namespace}...[/dim]") deployments = self.k8s_client.get_deployments(self.current_namespace) if not deployments: console.print("[yellow]No deployments found[/yellow]") return self.k8s_client.display_deployments_table(deployments) if questionary.confirm("View pods for a deployment?", style=custom_style, default=False).ask(): dep_names = [dep['name'] for dep in deployments] dep_name = questionary.select( "Select deployment:", choices=dep_names, style=custom_style ).ask() if dep_name: self._view_deployment_pods(dep_name) def _view_deployment_pods(self, deployment_name: str): """View pods for a deployment.""" label_selector = f"app={deployment_name}" console.print(f"[dim]Fetching pods for deployment {deployment_name}...[/dim]") pods = self.k8s_client.get_pods(self.current_namespace, label_selector=label_selector) if not pods: # Try without label selector console.print("[yellow]No pods found with label selector, fetching all pods...[/yellow]") pods = self.k8s_client.get_pods(self.current_namespace) if pods: self.k8s_client.display_pods_table(pods) else: console.print("[yellow]No pods found[/yellow]") def _restart_deployment(self): """Restart a deployment.""" if not self._ensure_namespace_selected(): return console.print(f"[dim]Fetching deployments in {self.current_namespace}...[/dim]") deployments = self.k8s_client.get_deployments(self.current_namespace) if not deployments: console.print("[yellow]No deployments found[/yellow]") return dep_names = [dep['name'] for dep in deployments] dep_name = questionary.select( "Select deployment to restart:", choices=dep_names, style=custom_style ).ask() if not dep_name: return confirm = questionary.confirm( f"Are you sure you want to restart {dep_name}?", style=custom_style, default=False ).ask() if confirm: self.k8s_client.restart_deployment(self.current_namespace, dep_name) def _scale_deployment(self): """Scale a deployment.""" if not self._ensure_namespace_selected(): return console.print(f"[dim]Fetching deployments in {self.current_namespace}...[/dim]") deployments = self.k8s_client.get_deployments(self.current_namespace) if not deployments: console.print("[yellow]No deployments found[/yellow]") return dep_names = [f"{dep['name']} (current: {dep['replicas']})" for dep in deployments] selected = questionary.select( "Select deployment to scale:", choices=dep_names, style=custom_style ).ask() if not selected: return dep_name = selected.split(" (current:")[0] replicas = questionary.text( "Enter number of replicas:", validate=lambda text: text.isdigit() and int(text) >= 0, style=custom_style ).ask() if replicas: self.k8s_client.scale_deployment(self.current_namespace, dep_name, int(replicas)) def _view_configmaps(self): """View ConfigMaps.""" 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 self.k8s_client.display_configmaps_table(configmaps) if questionary.confirm("View ConfigMap data?", style=custom_style, default=False).ask(): cm_names = [cm['name'] for cm in configmaps] cm_name = questionary.select( "Select ConfigMap:", choices=cm_names, style=custom_style ).ask() if cm_name: data = self.k8s_client.get_configmap_data(self.current_namespace, cm_name) if data: console.print(f"\n[bold]ConfigMap:[/bold] [cyan]{cm_name}[/cyan]\n") for key, value in data.items(): console.print(f"[bold cyan]--- {key} ---[/bold cyan]") console.print(Syntax(value, "yaml", line_numbers=False, background_color="default")) console.print() def _view_pod_logs(self): """View pod logs.""" if not self._ensure_namespace_selected(): return console.print(f"[dim]Fetching pods in {self.current_namespace}...[/dim]") pods = self.k8s_client.get_pods(self.current_namespace) if not pods: console.print("[yellow]No pods found[/yellow]") return pod_names = [f"{pod['name']} ({pod['status']})" for pod in pods] selected = questionary.select( "Select pod:", choices=pod_names, style=custom_style ).ask() if not selected: return pod_name = selected.split(" (")[0] # Check if pod has multiple containers containers = self.k8s_client.get_pod_containers(self.current_namespace, pod_name) container = None if len(containers) > 1: container = questionary.select( "Select container:", choices=containers, style=custom_style ).ask() elif len(containers) == 1: container = containers[0] tail_lines = questionary.text( "Number of lines to show (default: 100):", default="100", validate=lambda text: text.isdigit() and int(text) > 0, style=custom_style ).ask() if tail_lines: logs = self.k8s_client.get_pod_logs( self.current_namespace, pod_name, container=container, tail_lines=int(tail_lines) ) if logs: title = f"[bold cyan]Logs: {pod_name}" if container: title += f" [{container}]" title += "[/bold cyan]" console.print(f"\n{title}\n") console.print(Syntax(logs, "log", line_numbers=False, background_color="default")) console.print() def main(): """Main entry point.""" try: app = K8sTool() app.run() except KeyboardInterrupt: console.print("\n[yellow]Goodbye! 👋[/yellow]") sys.exit(0) except Exception as e: console.print(f"[red]Fatal error:[/red] {e}") sys.exit(1) if __name__ == "__main__": main()