336 lines
11 KiB
Python
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()
|