467 lines
15 KiB
Python
467 lines
15 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
|
|
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(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()
|