k8s-helper-cli 0.3.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
k8s_helper/__init__.py CHANGED
@@ -20,7 +20,7 @@ from .utils import (
20
20
  create_service_manifest
21
21
  )
22
22
 
23
- __version__ = "0.3.0"
23
+ __version__ = "0.4.0"
24
24
  __author__ = "Harshit Chatterjee"
25
25
  __email__ = "harshitchatterjee50@gmail.com"
26
26
 
k8s_helper/cli.py CHANGED
@@ -10,6 +10,7 @@ from rich.panel import Panel
10
10
  from rich.text import Text
11
11
  import time
12
12
  import time
13
+ from kubernetes.client.rest import ApiException
13
14
 
14
15
  from .core import K8sClient
15
16
  from .config import get_config
@@ -1348,3 +1349,505 @@ def service_url(
1348
1349
  console.print("\n👋 Stopped watching")
1349
1350
  else:
1350
1351
  show_service_url()
1352
+
1353
+
1354
+ # ======================
1355
+ # MONITORING COMMANDS
1356
+ # ======================
1357
+ @app.command()
1358
+ def setup_monitoring(
1359
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Namespace for monitoring stack"),
1360
+ grafana_service_type: str = typer.Option("NodePort", "--service-type", "-t", help="Grafana service type: NodePort, LoadBalancer, ClusterIP"),
1361
+ import_dashboard: bool = typer.Option(True, "--import-dashboard/--no-dashboard", help="Import default Kubernetes dashboard"),
1362
+ wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for deployments to be ready"),
1363
+ show_info: bool = typer.Option(True, "--show-info/--no-show-info", help="Show monitoring stack information after setup")
1364
+ ):
1365
+ """Deploy complete monitoring stack with Prometheus and Grafana"""
1366
+
1367
+ # Validate service type
1368
+ valid_service_types = ["NodePort", "LoadBalancer", "ClusterIP"]
1369
+ if grafana_service_type not in valid_service_types:
1370
+ console.print(f"❌ Invalid service type: {grafana_service_type}")
1371
+ console.print(f"💡 Valid options: {', '.join(valid_service_types)}")
1372
+ return
1373
+
1374
+ console.print(f"🚀 Setting up monitoring stack in namespace: {namespace}")
1375
+ console.print(f"🔧 Grafana service type: {grafana_service_type}")
1376
+
1377
+ if import_dashboard:
1378
+ console.print("📊 Will import default Kubernetes dashboard")
1379
+
1380
+ # Show what will be deployed
1381
+ console.print("\n📋 Components to deploy:")
1382
+ console.print(" • Prometheus server with cluster monitoring configuration")
1383
+ console.print(" • Grafana with admin credentials (admin/admin123)")
1384
+ console.print(" • ServiceAccount and RBAC for Prometheus")
1385
+ console.print(" • ConfigMaps for Prometheus configuration")
1386
+ if import_dashboard:
1387
+ console.print(" • Default Kubernetes metrics dashboard")
1388
+
1389
+ try:
1390
+ client = K8sClient()
1391
+
1392
+ with console.status("Deploying monitoring stack..."):
1393
+ result = client.setup_monitoring(
1394
+ namespace=namespace,
1395
+ grafana_service_type=grafana_service_type,
1396
+ import_dashboard=import_dashboard,
1397
+ wait_for_ready=wait
1398
+ )
1399
+
1400
+ if result['success']:
1401
+ console.print("✅ Monitoring stack deployed successfully!")
1402
+
1403
+ # Show deployment summary
1404
+ console.print(f"\n📋 Deployment Summary:")
1405
+ console.print(f"📍 Namespace: {result['namespace']}")
1406
+
1407
+ if result['prometheus']['deployed']:
1408
+ console.print("✅ Prometheus: Deployed")
1409
+ else:
1410
+ console.print("❌ Prometheus: Failed to deploy")
1411
+
1412
+ if result['grafana']['deployed']:
1413
+ console.print("✅ Grafana: Deployed")
1414
+ console.print(f"🔑 Admin credentials: {result['grafana']['admin_user']}/{result['grafana']['admin_password']}")
1415
+ else:
1416
+ console.print("❌ Grafana: Failed to deploy")
1417
+
1418
+ if show_info:
1419
+ # Get and display monitoring information
1420
+ with console.status("Retrieving monitoring stack information..."):
1421
+ time.sleep(2) # Give services time to be ready
1422
+ info = client.get_monitoring_info(namespace)
1423
+
1424
+ console.print(f"\n🔗 Monitoring Stack Information:")
1425
+
1426
+ # Prometheus info
1427
+ if info['prometheus']['running']:
1428
+ console.print(f"✅ Prometheus: Running")
1429
+ if info['prometheus']['url']:
1430
+ console.print(f"🔗 Prometheus URL: [blue]{info['prometheus']['url']}[/blue]")
1431
+ else:
1432
+ console.print(f"💡 Prometheus: Access via port-forward: kubectl port-forward -n {namespace} svc/prometheus-service 9090:9090")
1433
+ else:
1434
+ console.print(f"⏳ Prometheus: Starting up...")
1435
+
1436
+ # Grafana info
1437
+ if info['grafana']['running']:
1438
+ console.print(f"✅ Grafana: Running")
1439
+ if info['grafana']['url']:
1440
+ console.print(f"🔗 Grafana URL: [blue]{info['grafana']['url']}[/blue]")
1441
+ else:
1442
+ console.print(f"💡 Grafana: Access via port-forward: kubectl port-forward -n {namespace} svc/grafana-service 3000:3000")
1443
+
1444
+ if info['grafana']['credentials']:
1445
+ creds = info['grafana']['credentials']
1446
+ console.print(f"🔑 Login: [green]{creds['username']}[/green] / [green]{creds['password']}[/green]")
1447
+ else:
1448
+ console.print(f"⏳ Grafana: Starting up...")
1449
+
1450
+ # Show services
1451
+ console.print(f"\n📋 Services:")
1452
+ for service in info['services']:
1453
+ console.print(f" • {service['name']} ({service['type']}) - IP: {service['cluster_ip']}")
1454
+ for port in service['ports']:
1455
+ port_info = f" Port: {port['port']} → {port['target_port']}"
1456
+ if 'node_port' in port:
1457
+ port_info += f" (NodePort: {port['node_port']})"
1458
+ console.print(port_info)
1459
+
1460
+ # Show next steps
1461
+ console.print(f"\n🚀 Next Steps:")
1462
+ console.print(f" 1. Access Grafana with the provided URL and credentials")
1463
+ console.print(f" 2. Verify Prometheus data source is configured")
1464
+ console.print(f" 3. Explore the imported Kubernetes dashboard")
1465
+ console.print(f" 4. Create custom dashboards and alerts as needed")
1466
+ console.print(f"\n💡 Useful commands:")
1467
+ console.print(f" • Check status: k8s-helper monitoring-status -n {namespace}")
1468
+ console.print(f" • Port-forward Grafana: kubectl port-forward -n {namespace} svc/grafana-service 3000:3000")
1469
+ console.print(f" • Port-forward Prometheus: kubectl port-forward -n {namespace} svc/prometheus-service 9090:9090")
1470
+
1471
+ else:
1472
+ console.print(f"❌ Failed to deploy monitoring stack: {result.get('error', 'Unknown error')}")
1473
+
1474
+ # Show partial deployment info
1475
+ if result['prometheus']['deployed']:
1476
+ console.print("✅ Prometheus was deployed successfully")
1477
+ if result['grafana']['deployed']:
1478
+ console.print("✅ Grafana was deployed successfully")
1479
+
1480
+ console.print("\n🛠️ Troubleshooting:")
1481
+ console.print(" • Check cluster connectivity: kubectl get nodes")
1482
+ console.print(" • Verify namespace permissions")
1483
+ console.print(" • Check resource availability in the cluster")
1484
+ console.print(f" • View monitoring namespace: kubectl get all -n {namespace}")
1485
+
1486
+ except Exception as e:
1487
+ console.print(f"❌ Error setting up monitoring: {e}")
1488
+ console.print("\n🛠️ Troubleshooting:")
1489
+ console.print(" • Ensure kubectl is configured and cluster is accessible")
1490
+ console.print(" • Check if you have sufficient permissions to create resources")
1491
+ console.print(" • Verify the cluster has enough resources available")
1492
+
1493
+
1494
+ @app.command()
1495
+ def monitoring_status(
1496
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
1497
+ output: str = output_option
1498
+ ):
1499
+ """Show status of monitoring stack"""
1500
+ try:
1501
+ client = K8sClient()
1502
+
1503
+ with console.status("Checking monitoring stack status..."):
1504
+ info = client.get_monitoring_info(namespace)
1505
+
1506
+ if 'error' in info:
1507
+ console.print(f"❌ Error getting monitoring status: {info['error']}")
1508
+ return
1509
+
1510
+ if output == "table":
1511
+ # Overview table
1512
+ table = Table(title=f"Monitoring Stack Status - {namespace}")
1513
+ table.add_column("Component", style="cyan")
1514
+ table.add_column("Status", style="green")
1515
+ table.add_column("URL", style="blue")
1516
+
1517
+ # Prometheus row
1518
+ prometheus_status = "🟢 Running" if info['prometheus']['running'] else "🔴 Not Running"
1519
+ prometheus_url = info['prometheus']['url'] or "Port-forward required"
1520
+ table.add_row("Prometheus", prometheus_status, prometheus_url)
1521
+
1522
+ # Grafana row
1523
+ grafana_status = "🟢 Running" if info['grafana']['running'] else "🔴 Not Running"
1524
+ grafana_url = info['grafana']['url'] or "Port-forward required"
1525
+ table.add_row("Grafana", grafana_status, grafana_url)
1526
+
1527
+ console.print(table)
1528
+
1529
+ # Credentials info
1530
+ if info['grafana']['credentials']:
1531
+ creds = info['grafana']['credentials']
1532
+ console.print(f"\n🔑 Grafana Credentials:")
1533
+ console.print(f" Username: [green]{creds['username']}[/green]")
1534
+ console.print(f" Password: [green]{creds['password']}[/green]")
1535
+
1536
+ # Services table
1537
+ if info['services']:
1538
+ services_table = Table(title="Services")
1539
+ services_table.add_column("Name", style="cyan")
1540
+ services_table.add_column("Type", style="magenta")
1541
+ services_table.add_column("Cluster IP", style="yellow")
1542
+ services_table.add_column("Ports", style="green")
1543
+
1544
+ for service in info['services']:
1545
+ ports_str = ", ".join([
1546
+ f"{port['port']}:{port['target_port']}" +
1547
+ (f" (NodePort: {port['node_port']})" if 'node_port' in port else "")
1548
+ for port in service['ports']
1549
+ ])
1550
+ services_table.add_row(
1551
+ service['name'],
1552
+ service['type'],
1553
+ service['cluster_ip'],
1554
+ ports_str
1555
+ )
1556
+
1557
+ console.print(services_table)
1558
+
1559
+ elif output == "json":
1560
+ console.print(format_json_output(info))
1561
+ elif output == "yaml":
1562
+ console.print(format_yaml_output(info))
1563
+
1564
+ except Exception as e:
1565
+ console.print(f"❌ Error checking monitoring status: {e}")
1566
+
1567
+
1568
+ @app.command()
1569
+ def delete_monitoring(
1570
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
1571
+ force: bool = typer.Option(False, "--force", help="Skip confirmation prompt")
1572
+ ):
1573
+ """Delete monitoring stack"""
1574
+ if not force:
1575
+ if not typer.confirm(f"Are you sure you want to delete the monitoring stack in namespace '{namespace}'?"):
1576
+ console.print("❌ Operation cancelled")
1577
+ return
1578
+
1579
+ try:
1580
+ client = K8sClient()
1581
+
1582
+ console.print(f"🗑️ Deleting monitoring stack from namespace: {namespace}")
1583
+
1584
+ # Delete deployments
1585
+ deployments_to_delete = ["prometheus", "grafana"]
1586
+ for deployment in deployments_to_delete:
1587
+ try:
1588
+ with console.status(f"Deleting deployment {deployment}..."):
1589
+ client.apps_v1.delete_namespaced_deployment(name=deployment, namespace=namespace)
1590
+ console.print(f"✅ Deleted deployment: {deployment}")
1591
+ except ApiException as e:
1592
+ if e.status == 404:
1593
+ console.print(f"⚠️ Deployment {deployment} not found")
1594
+ else:
1595
+ console.print(f"❌ Error deleting deployment {deployment}: {e}")
1596
+
1597
+ # Delete services
1598
+ services_to_delete = ["prometheus-service", "grafana-service"]
1599
+ for service in services_to_delete:
1600
+ try:
1601
+ with console.status(f"Deleting service {service}..."):
1602
+ client.core_v1.delete_namespaced_service(name=service, namespace=namespace)
1603
+ console.print(f"✅ Deleted service: {service}")
1604
+ except ApiException as e:
1605
+ if e.status == 404:
1606
+ console.print(f"⚠️ Service {service} not found")
1607
+ else:
1608
+ console.print(f"❌ Error deleting service {service}: {e}")
1609
+
1610
+ # Delete ConfigMaps
1611
+ configmaps_to_delete = ["prometheus-config"]
1612
+ for configmap in configmaps_to_delete:
1613
+ try:
1614
+ with console.status(f"Deleting configmap {configmap}..."):
1615
+ client.core_v1.delete_namespaced_config_map(name=configmap, namespace=namespace)
1616
+ console.print(f"✅ Deleted configmap: {configmap}")
1617
+ except ApiException as e:
1618
+ if e.status == 404:
1619
+ console.print(f"⚠️ ConfigMap {configmap} not found")
1620
+ else:
1621
+ console.print(f"❌ Error deleting configmap {configmap}: {e}")
1622
+
1623
+ # Delete ServiceAccount
1624
+ try:
1625
+ with console.status("Deleting ServiceAccount prometheus..."):
1626
+ client.core_v1.delete_namespaced_service_account(name="prometheus", namespace=namespace)
1627
+ console.print("✅ Deleted ServiceAccount: prometheus")
1628
+ except ApiException as e:
1629
+ if e.status == 404:
1630
+ console.print("⚠️ ServiceAccount prometheus not found")
1631
+ else:
1632
+ console.print(f"❌ Error deleting ServiceAccount: {e}")
1633
+
1634
+ # Delete RBAC resources
1635
+ try:
1636
+ from kubernetes import client as k8s_client
1637
+ rbac_v1 = k8s_client.RbacAuthorizationV1Api()
1638
+ with console.status("Deleting RBAC resources..."):
1639
+ rbac_v1.delete_cluster_role(name="prometheus")
1640
+ rbac_v1.delete_cluster_role_binding(name="prometheus")
1641
+ console.print("✅ Deleted RBAC resources")
1642
+ except ApiException as e:
1643
+ if e.status == 404:
1644
+ console.print("⚠️ RBAC resources not found")
1645
+ else:
1646
+ console.print(f"❌ Error deleting RBAC resources: {e}")
1647
+
1648
+ console.print(f"✅ Monitoring stack cleanup completed for namespace: {namespace}")
1649
+ console.print(f"💡 To delete the namespace entirely: kubectl delete namespace {namespace}")
1650
+
1651
+ except Exception as e:
1652
+ console.print(f"❌ Error deleting monitoring stack: {e}")
1653
+ console.print("💡 You may need to clean up resources manually with kubectl")
1654
+
1655
+
1656
+ @app.command()
1657
+ def add_prometheus_target(
1658
+ job_name: str = typer.Argument(..., help="Name for the Prometheus job"),
1659
+ targets: str = typer.Argument(..., help="Comma-separated list of targets (e.g., 'service:port,1.2.3.4:9090')"),
1660
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
1661
+ metrics_path: str = typer.Option("/metrics", "--metrics-path", "-p", help="Path to metrics endpoint"),
1662
+ scrape_interval: str = typer.Option("15s", "--scrape-interval", "-i", help="Scrape interval (e.g., 15s, 30s, 1m)")
1663
+ ):
1664
+ """Add a new target to Prometheus monitoring"""
1665
+ if not validate_name(job_name):
1666
+ console.print(f"❌ Invalid job name: {job_name}")
1667
+ return
1668
+
1669
+ # Parse targets
1670
+ target_list = [target.strip() for target in targets.split(",") if target.strip()]
1671
+
1672
+ if not target_list:
1673
+ console.print("❌ No valid targets provided")
1674
+ return
1675
+
1676
+ console.print(f"🎯 Adding Prometheus target job: {job_name}")
1677
+ console.print(f"📋 Targets: {target_list}")
1678
+ console.print(f"📍 Namespace: {namespace}")
1679
+ console.print(f"🛣️ Metrics path: {metrics_path}")
1680
+ console.print(f"⏱️ Scrape interval: {scrape_interval}")
1681
+
1682
+ try:
1683
+ client = K8sClient()
1684
+
1685
+ with console.status("Adding Prometheus target..."):
1686
+ success = client.add_prometheus_target(
1687
+ job_name=job_name,
1688
+ targets=target_list,
1689
+ namespace=namespace,
1690
+ metrics_path=metrics_path,
1691
+ scrape_interval=scrape_interval
1692
+ )
1693
+
1694
+ if success:
1695
+ console.print(f"✅ Successfully added Prometheus target job '{job_name}'")
1696
+ console.print(f"🔄 Prometheus configuration updated and deployment restarted")
1697
+ console.print(f"\n💡 Next steps:")
1698
+ console.print(f" • Check targets in Prometheus UI: Status > Targets")
1699
+ console.print(f" • Verify metrics are being scraped")
1700
+ console.print(f" • Create Grafana dashboards for the new metrics")
1701
+ else:
1702
+ console.print(f"❌ Failed to add Prometheus target")
1703
+
1704
+ except Exception as e:
1705
+ console.print(f"❌ Error adding Prometheus target: {e}")
1706
+
1707
+
1708
+ @app.command()
1709
+ def remove_prometheus_target(
1710
+ job_name: str = typer.Argument(..., help="Name of the Prometheus job to remove"),
1711
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
1712
+ force: bool = typer.Option(False, "--force", help="Skip confirmation prompt")
1713
+ ):
1714
+ """Remove a target from Prometheus monitoring"""
1715
+ if not force:
1716
+ if not typer.confirm(f"Are you sure you want to remove Prometheus job '{job_name}'?"):
1717
+ console.print("❌ Operation cancelled")
1718
+ return
1719
+
1720
+ console.print(f"🗑️ Removing Prometheus target job: {job_name}")
1721
+ console.print(f"📍 Namespace: {namespace}")
1722
+
1723
+ try:
1724
+ client = K8sClient()
1725
+
1726
+ with console.status("Removing Prometheus target..."):
1727
+ success = client.remove_prometheus_target(
1728
+ job_name=job_name,
1729
+ namespace=namespace
1730
+ )
1731
+
1732
+ if success:
1733
+ console.print(f"✅ Successfully removed Prometheus target job '{job_name}'")
1734
+ console.print(f"🔄 Prometheus configuration updated and deployment restarted")
1735
+ else:
1736
+ console.print(f"❌ Failed to remove Prometheus target")
1737
+
1738
+ except Exception as e:
1739
+ console.print(f"❌ Error removing Prometheus target: {e}")
1740
+
1741
+
1742
+ @app.command()
1743
+ def list_prometheus_targets(
1744
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
1745
+ output: str = output_option
1746
+ ):
1747
+ """List all Prometheus monitoring targets"""
1748
+ try:
1749
+ client = K8sClient()
1750
+
1751
+ with console.status("Fetching Prometheus targets..."):
1752
+ result = client.list_prometheus_targets(namespace)
1753
+
1754
+ if 'error' in result:
1755
+ console.print(f"❌ Error getting Prometheus targets: {result['error']}")
1756
+ return
1757
+
1758
+ targets = result.get('targets', [])
1759
+
1760
+ if not targets:
1761
+ console.print(f"📋 No Prometheus targets found in namespace: {namespace}")
1762
+ console.print(f"💡 Add targets with: k8s-helper add-prometheus-target <job-name> <targets>")
1763
+ return
1764
+
1765
+ if output == "table":
1766
+ table = Table(title=f"Prometheus Targets - {namespace}")
1767
+ table.add_column("Job Name", style="cyan")
1768
+ table.add_column("Type", style="magenta")
1769
+ table.add_column("Targets", style="green")
1770
+ table.add_column("Metrics Path", style="blue")
1771
+ table.add_column("Scrape Interval", style="yellow")
1772
+
1773
+ for target in targets:
1774
+ job_type = target.get('type', 'static')
1775
+ job_type_display = "🔄 Kubernetes" if job_type == 'kubernetes_discovery' else "📍 Static"
1776
+
1777
+ targets_display = ", ".join(target.get('targets', []))
1778
+ if len(targets_display) > 50:
1779
+ targets_display = targets_display[:47] + "..."
1780
+
1781
+ table.add_row(
1782
+ target.get('job_name', 'unknown'),
1783
+ job_type_display,
1784
+ targets_display,
1785
+ target.get('metrics_path', '/metrics'),
1786
+ target.get('scrape_interval', 'default')
1787
+ )
1788
+
1789
+ console.print(table)
1790
+ console.print(f"\n📊 Total jobs: {result.get('total_jobs', 0)}")
1791
+
1792
+ elif output == "json":
1793
+ console.print(format_json_output(result))
1794
+ elif output == "yaml":
1795
+ console.print(format_yaml_output(result))
1796
+
1797
+ except Exception as e:
1798
+ console.print(f"❌ Error listing Prometheus targets: {e}")
1799
+
1800
+
1801
+ @app.command()
1802
+ def update_prometheus_target(
1803
+ job_name: str = typer.Argument(..., help="Name of the Prometheus job to update"),
1804
+ targets: str = typer.Argument(..., help="New comma-separated list of targets"),
1805
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
1806
+ metrics_path: Optional[str] = typer.Option(None, "--metrics-path", "-p", help="New metrics path"),
1807
+ scrape_interval: Optional[str] = typer.Option(None, "--scrape-interval", "-i", help="New scrape interval")
1808
+ ):
1809
+ """Update an existing Prometheus target"""
1810
+ if not validate_name(job_name):
1811
+ console.print(f"❌ Invalid job name: {job_name}")
1812
+ return
1813
+
1814
+ # Parse targets
1815
+ target_list = [target.strip() for target in targets.split(",") if target.strip()]
1816
+
1817
+ if not target_list:
1818
+ console.print("❌ No valid targets provided")
1819
+ return
1820
+
1821
+ console.print(f"🔄 Updating Prometheus target job: {job_name}")
1822
+ console.print(f"📋 New targets: {target_list}")
1823
+ console.print(f"📍 Namespace: {namespace}")
1824
+
1825
+ if metrics_path:
1826
+ console.print(f"🛣️ New metrics path: {metrics_path}")
1827
+ if scrape_interval:
1828
+ console.print(f"⏱️ New scrape interval: {scrape_interval}")
1829
+
1830
+ try:
1831
+ client = K8sClient()
1832
+
1833
+ with console.status("Updating Prometheus target..."):
1834
+ success = client.update_prometheus_target(
1835
+ job_name=job_name,
1836
+ targets=target_list,
1837
+ namespace=namespace,
1838
+ metrics_path=metrics_path,
1839
+ scrape_interval=scrape_interval
1840
+ )
1841
+
1842
+ if success:
1843
+ console.print(f"✅ Successfully updated Prometheus target job '{job_name}'")
1844
+ console.print(f"🔄 Prometheus configuration updated and deployment restarted")
1845
+ else:
1846
+ console.print(f"❌ Failed to update Prometheus target")
1847
+
1848
+ except Exception as e:
1849
+ console.print(f"❌ Error updating Prometheus target: {e}")
1850
+
1851
+
1852
+ if __name__ == "__main__":
1853
+ app()