configmaps editing

This commit is contained in:
vy.boyko 2025-10-25 21:28:56 +03:00
parent 75a30aba8b
commit 36bcbb6847
4 changed files with 136 additions and 3 deletions

View File

@ -10,7 +10,7 @@
- 📦 Управление namespaces, deployments, pods, ConfigMaps - 📦 Управление namespaces, deployments, pods, ConfigMaps
- 🔄 Быстрый рестарт и масштабирование deployments - 🔄 Быстрый рестарт и масштабирование deployments
- 📝 Просмотр логов pods - 📝 Просмотр логов pods
- 🔍 Просмотр содержимого ConfigMaps - 🔍 Просмотр и редактирование ConfigMaps в текстовом редакторе
- 💾 Сохранение настроек в `~/.config/k8s-tool/k8s-tool.cfg` - 💾 Сохранение настроек в `~/.config/k8s-tool/k8s-tool.cfg`
## Требования ## Требования
@ -95,6 +95,14 @@ poetry run k8s-tool
Можно выбрать ConfigMap для просмотра его содержимого с подсветкой синтаксиса. Можно выбрать ConfigMap для просмотра его содержимого с подсветкой синтаксиса.
#### Редактирование ConfigMap
Отдельный пункт меню для редактирования ConfigMaps:
- Выберите ConfigMap из списка
- Открывается текстовый редактор (из переменной окружения `$EDITOR` или `$VISUAL`, по умолчанию `vi`)
- Редактируйте YAML с данными ConfigMap
- После сохранения показывается diff изменений (добавленные, удаленные, измененные ключи)
- Подтверждение перед применением изменений в кластер
#### Просмотр логов Pod #### Просмотр логов Pod
Выберите pod и контейнер (если их несколько) для просмотра логов. Можно указать количество строк для отображения (по умолчанию 100). Выберите pod и контейнер (если их несколько) для просмотра логов. Можно указать количество строк для отображения (по умолчанию 100).

View File

@ -89,6 +89,21 @@ class K8sClient:
console.print(f"[red]Error reading ConfigMap:[/red] {e}") console.print(f"[red]Error reading ConfigMap:[/red] {e}")
return None 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: def restart_deployment(self, namespace: str, name: str) -> bool:
"""Restart deployment by updating annotation.""" """Restart deployment by updating annotation."""
try: try:

View File

@ -1,7 +1,11 @@
"""Main application entry point.""" """Main application entry point."""
import sys import sys
from typing import Optional import os
import subprocess
import tempfile
import yaml
from typing import Optional, Dict
import questionary import questionary
from questionary import Style from questionary import Style
from rich.console import Console from rich.console import Console
@ -71,6 +75,7 @@ class K8sTool:
"Restart Deployment", "Restart Deployment",
"Scale Deployment", "Scale Deployment",
"View ConfigMaps", "View ConfigMaps",
"Edit ConfigMap",
"View Pod Logs", "View Pod Logs",
"Exit" "Exit"
] ]
@ -93,6 +98,8 @@ class K8sTool:
self._scale_deployment() self._scale_deployment()
elif action == "View ConfigMaps": elif action == "View ConfigMaps":
self._view_configmaps() self._view_configmaps()
elif action == "Edit ConfigMap":
self._edit_configmaps()
elif action == "View Pod Logs": elif action == "View Pod Logs":
self._view_pod_logs() self._view_pod_logs()
elif action == "Exit": elif action == "Exit":
@ -386,6 +393,108 @@ class K8sTool:
console.print(Syntax(value, "yaml", line_numbers=False, background_color="default")) console.print(Syntax(value, "yaml", line_numbers=False, background_color="default"))
console.print() 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): def _view_pod_logs(self):
"""View pod logs.""" """View pod logs."""
if not self._ensure_namespace_selected(): if not self._ensure_namespace_selected():

View File

@ -10,7 +10,8 @@ requires-python = "^3.9"
dependencies = [ dependencies = [
"questionary (>=2.1.1,<3.0.0)", "questionary (>=2.1.1,<3.0.0)",
"kubernetes (>=34.1.0,<35.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] [project.scripts]