k8s-tool/k8s_tool/main.py
2025-10-25 20:59:29 +03:00

336 lines
11 KiB
Python

"""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
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.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]"
console.print(f"\n[bold]Current namespace:[/bold] {namespace_info}")
choices = [
"Select Namespace",
"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 == "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
namespace = questionary.select(
"Select namespace:",
choices=namespaces,
style=custom_style
).ask()
if namespace:
self.current_namespace = namespace
console.print(f"[green]✓[/green] Namespace set to: [cyan]{namespace}[/cyan]")
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(Panel(
Syntax(value, "yaml", theme="monokai", line_numbers=True),
title=f"[cyan]{key}[/cyan]",
border_style="cyan"
))
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:
console.print(Panel(
Syntax(logs, "log", theme="monokai", line_numbers=False),
title=f"[cyan]Logs: {pod_name}" + (f" [{container}]" if container else "") + "[/cyan]",
border_style="cyan",
expand=False
))
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()