k8s-helper-cli 0.5.0__py3-none-any.whl → 0.5.2__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.5.0"
23
+ __version__ = "0.5.2"
24
24
  __author__ = "Harshit Chatterjee"
25
25
  __email__ = "harshitchatterjee50@gmail.com"
26
26
 
k8s_helper/cli.py CHANGED
@@ -1886,5 +1886,279 @@ def update_prometheus_target(
1886
1886
  console.print(f"❌ Error updating Prometheus target: {e}")
1887
1887
 
1888
1888
 
1889
+ # ======================
1890
+ # HELM-BASED MONITORING COMMANDS
1891
+ # ======================
1892
+ @app.command()
1893
+ def setup_monitoring_stack(
1894
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Namespace for monitoring stack"),
1895
+ grafana_service_type: str = typer.Option("NodePort", "--service-type", "-t", help="Grafana service type: NodePort, LoadBalancer, ClusterIP"),
1896
+ prometheus_storage_size: str = typer.Option("10Gi", "--prometheus-storage", help="Prometheus storage size"),
1897
+ grafana_storage_size: str = typer.Option("5Gi", "--grafana-storage", help="Grafana storage size"),
1898
+ wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for deployments to be ready"),
1899
+ install_ingress: bool = typer.Option(False, "--install-ingress", help="Install ingress for external access")
1900
+ ):
1901
+ """Deploy monitoring stack using official Helm charts (auto-installs Helm if needed)"""
1902
+
1903
+ # Validate service type
1904
+ valid_service_types = ["NodePort", "LoadBalancer", "ClusterIP"]
1905
+ if grafana_service_type not in valid_service_types:
1906
+ console.print(f"❌ Invalid service type: {grafana_service_type}")
1907
+ console.print(f"💡 Valid options: {', '.join(valid_service_types)}")
1908
+ return
1909
+
1910
+ console.print(f"🚀 Setting up Helm-based monitoring stack in namespace: {namespace}")
1911
+ console.print(f"🔧 Grafana service type: {grafana_service_type}")
1912
+ console.print(f"💾 Prometheus storage: {prometheus_storage_size}")
1913
+ console.print(f"💾 Grafana storage: {grafana_storage_size}")
1914
+ console.print(f"⚙️ Note: Helm will be automatically installed if not present")
1915
+
1916
+ # Show what will be deployed
1917
+ console.print("\n📋 Components to deploy via Helm:")
1918
+ console.print(" • Prometheus Operator (kube-prometheus-stack)")
1919
+ console.print(" • Grafana with persistent storage")
1920
+ console.print(" • AlertManager for alerts")
1921
+ console.print(" • Node Exporter for node metrics")
1922
+ console.print(" • kube-state-metrics for cluster state")
1923
+ console.print(" • Prometheus rules and dashboards")
1924
+ if install_ingress:
1925
+ console.print(" • Ingress for external access")
1926
+
1927
+ try:
1928
+ client = K8sClient()
1929
+
1930
+ with console.status("Deploying Helm monitoring stack..."):
1931
+ result = client.setup_helm_monitoring(
1932
+ namespace=namespace,
1933
+ grafana_service_type=grafana_service_type,
1934
+ prometheus_storage_size=prometheus_storage_size,
1935
+ grafana_storage_size=grafana_storage_size,
1936
+ wait_for_ready=wait,
1937
+ install_ingress=install_ingress
1938
+ )
1939
+
1940
+ if result['success']:
1941
+ console.print("✅ Helm monitoring stack deployed successfully!")
1942
+
1943
+ # Show deployment summary
1944
+ console.print(f"\n📋 Deployment Summary:")
1945
+ console.print(f"📍 Namespace: {result['namespace']}")
1946
+ console.print(f"🎯 Helm Release: {result['release_name']}")
1947
+
1948
+ if result.get('prometheus', {}).get('deployed'):
1949
+ console.print("✅ Prometheus Operator: Deployed")
1950
+ else:
1951
+ console.print("❌ Prometheus Operator: Failed to deploy")
1952
+
1953
+ if result.get('grafana', {}).get('deployed'):
1954
+ console.print("✅ Grafana: Deployed")
1955
+ if result['grafana'].get('admin_password'):
1956
+ console.print(f"🔑 Grafana admin password: {result['grafana']['admin_password']}")
1957
+ else:
1958
+ console.print("🔑 Grafana admin password: admin")
1959
+ else:
1960
+ console.print("❌ Grafana: Failed to deploy")
1961
+
1962
+ # Show access information
1963
+ console.print(f"\n🔗 Access Information:")
1964
+
1965
+ if result.get('grafana_url'):
1966
+ console.print(f"🔗 Grafana URL: [blue]{result['grafana_url']}[/blue]")
1967
+ else:
1968
+ console.print(f"💡 Grafana: kubectl port-forward -n {namespace} svc/kube-prometheus-stack-grafana 3000:80")
1969
+
1970
+ if result.get('prometheus_url'):
1971
+ console.print(f"🔗 Prometheus URL: [blue]{result['prometheus_url']}[/blue]")
1972
+ else:
1973
+ console.print(f"💡 Prometheus: kubectl port-forward -n {namespace} svc/kube-prometheus-stack-prometheus 9090:9090")
1974
+
1975
+ if result.get('alertmanager_url'):
1976
+ console.print(f"🔗 AlertManager URL: [blue]{result['alertmanager_url']}[/blue]")
1977
+ else:
1978
+ console.print(f"💡 AlertManager: kubectl port-forward -n {namespace} svc/kube-prometheus-stack-alertmanager 9093:9093")
1979
+
1980
+ # Show next steps
1981
+ console.print(f"\n🚀 Next Steps:")
1982
+ console.print(f" 1. Access Grafana with admin/admin (or password shown above)")
1983
+ console.print(f" 2. Explore pre-configured dashboards")
1984
+ console.print(f" 3. Set up custom alerts in AlertManager")
1985
+ console.print(f" 4. Add custom Prometheus targets if needed")
1986
+ console.print(f"\n💡 Useful commands:")
1987
+ console.print(f" • Check status: k8s-helper monitoring-stack-status -n {namespace}")
1988
+ console.print(f" • List dashboards: kubectl get configmaps -n {namespace} | grep dashboard")
1989
+ console.print(f" • View Helm release: helm list -n {namespace}")
1990
+
1991
+ else:
1992
+ console.print(f"❌ Failed to deploy Helm monitoring stack: {result.get('error', 'Unknown error')}")
1993
+
1994
+ # Show suggestion if provided
1995
+ if result.get('suggestion'):
1996
+ console.print(f"💡 {result['suggestion']}")
1997
+
1998
+ console.print("\n🛠️ Troubleshooting:")
1999
+ if 'Helm' in str(result.get('error', '')):
2000
+ console.print(" • Helm installation failed - you may need to install manually")
2001
+ console.print(" • Visit: https://helm.sh/docs/intro/install/")
2002
+ console.print(" • Restart your terminal after installation")
2003
+ else:
2004
+ console.print(" • Check cluster connectivity: kubectl get nodes")
2005
+ console.print(" • Verify namespace permissions")
2006
+ console.print(f" • View Helm status: helm status -n {namespace} kube-prometheus-stack")
2007
+
2008
+ except Exception as e:
2009
+ console.print(f"❌ Error setting up Helm monitoring: {e}")
2010
+ console.print("\n🛠️ Troubleshooting:")
2011
+ console.print(" • Ensure Helm is installed and configured")
2012
+ console.print(" • Check if kubectl is configured correctly")
2013
+ console.print(" • Verify you have cluster admin permissions")
2014
+
2015
+
2016
+ @app.command()
2017
+ def monitoring_stack_status(
2018
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
2019
+ output: str = output_option
2020
+ ):
2021
+ """Show status of Helm-based monitoring stack"""
2022
+ try:
2023
+ client = K8sClient()
2024
+
2025
+ with console.status("Checking Helm monitoring stack status..."):
2026
+ info = client.get_helm_monitoring_info(namespace)
2027
+
2028
+ if 'error' in info:
2029
+ console.print(f"❌ Error getting monitoring status: {info['error']}")
2030
+ return
2031
+
2032
+ if output == "table":
2033
+ # Helm release info
2034
+ console.print(f"🎯 Helm Release: {info.get('release_name', 'kube-prometheus-stack')}")
2035
+ console.print(f"📊 Release Status: {info.get('release_status', 'Unknown')}")
2036
+ console.print(f"📅 Last Deployed: {info.get('last_deployed', 'Unknown')}")
2037
+
2038
+ # Overview table
2039
+ table = Table(title=f"Monitoring Stack Status - {namespace}")
2040
+ table.add_column("Component", style="cyan")
2041
+ table.add_column("Status", style="green")
2042
+ table.add_column("URL", style="blue")
2043
+
2044
+ # Components status
2045
+ components = ['prometheus', 'grafana', 'alertmanager']
2046
+ for component in components:
2047
+ if component in info:
2048
+ comp_info = info[component]
2049
+ status = "🟢 Running" if comp_info.get('running') else "🔴 Not Running"
2050
+ url = comp_info.get('url', 'Port-forward required')
2051
+ table.add_row(component.capitalize(), status, url)
2052
+
2053
+ console.print(table)
2054
+
2055
+ # Show pod status
2056
+ if info.get('pods'):
2057
+ pod_table = Table(title="Pod Status")
2058
+ pod_table.add_column("Pod", style="cyan")
2059
+ pod_table.add_column("Status", style="green")
2060
+ pod_table.add_column("Ready", style="blue")
2061
+
2062
+ for pod in info['pods']:
2063
+ pod_table.add_row(
2064
+ pod['name'],
2065
+ pod['status'],
2066
+ f"{pod['ready']}/{pod['total']}"
2067
+ )
2068
+
2069
+ console.print(pod_table)
2070
+
2071
+ elif output == "json":
2072
+ console.print(format_json_output(info))
2073
+ elif output == "yaml":
2074
+ console.print(format_yaml_output(info))
2075
+
2076
+ except Exception as e:
2077
+ console.print(f"❌ Error checking Helm monitoring status: {e}")
2078
+
2079
+
2080
+ @app.command()
2081
+ def delete_monitoring_stack(
2082
+ namespace: str = typer.Option("monitoring", "--namespace", "-n", help="Monitoring namespace"),
2083
+ release_name: str = typer.Option("kube-prometheus-stack", "--release-name", help="Helm release name"),
2084
+ force: bool = typer.Option(False, "--force", help="Skip confirmation prompt")
2085
+ ):
2086
+ """Delete Helm-based monitoring stack"""
2087
+ if not force:
2088
+ if not typer.confirm(f"Are you sure you want to delete the Helm monitoring stack '{release_name}' in namespace '{namespace}'?"):
2089
+ console.print("❌ Operation cancelled")
2090
+ return
2091
+
2092
+ try:
2093
+ client = K8sClient()
2094
+
2095
+ console.print(f"🗑️ Deleting Helm monitoring stack: {release_name}")
2096
+
2097
+ with console.status("Uninstalling Helm release..."):
2098
+ result = client.delete_helm_monitoring(namespace, release_name)
2099
+
2100
+ if result['success']:
2101
+ console.print(f"✅ Helm monitoring stack '{release_name}' deleted successfully")
2102
+ console.print(f"📋 Cleaned up {result.get('resources_deleted', 0)} resources")
2103
+ else:
2104
+ console.print(f"❌ Failed to delete Helm monitoring stack: {result.get('error', 'Unknown error')}")
2105
+
2106
+ except Exception as e:
2107
+ console.print(f"❌ Error deleting Helm monitoring: {e}")
2108
+
2109
+
2110
+ @app.command()
2111
+ def install_helm(
2112
+ force: bool = typer.Option(False, "--force", help="Force reinstall even if Helm is already installed")
2113
+ ):
2114
+ """Install Helm automatically on your system"""
2115
+ try:
2116
+ client = K8sClient()
2117
+
2118
+ # Check if Helm is already installed
2119
+ if not force and client._check_helm_available():
2120
+ console.print("✅ Helm is already installed on your system")
2121
+
2122
+ import subprocess
2123
+ try:
2124
+ result = subprocess.run(['helm', 'version'], capture_output=True, text=True)
2125
+ console.print(f"📋 Version: {result.stdout.strip()}")
2126
+ except:
2127
+ pass
2128
+
2129
+ console.print("💡 Use --force to reinstall")
2130
+ return
2131
+
2132
+ console.print("🔧 Installing Helm automatically...")
2133
+
2134
+ with console.status("Installing Helm..."):
2135
+ result = client._install_helm_automatically()
2136
+
2137
+ if result['success']:
2138
+ console.print(f"✅ {result['message']}")
2139
+ console.print(f"📦 Installation method: {result['method']}")
2140
+
2141
+ # Test the installation
2142
+ if client._check_helm_available():
2143
+ console.print("✅ Helm installation verified successfully")
2144
+ console.print("\n🚀 You can now use Helm-based monitoring commands:")
2145
+ console.print(" • k8s-helper setup-monitoring-stack")
2146
+ console.print(" • k8s-helper monitoring-stack-status")
2147
+ console.print(" • k8s-helper delete-monitoring-stack")
2148
+ else:
2149
+ console.print("⚠️ Helm was installed but may not be in your PATH")
2150
+ console.print("💡 You may need to restart your terminal")
2151
+ else:
2152
+ console.print(f"❌ Failed to install Helm: {result.get('error', 'Unknown error')}")
2153
+ console.print("\n🛠️ Manual installation:")
2154
+ console.print(" • Windows: choco install kubernetes-helm")
2155
+ console.print(" • macOS: brew install helm")
2156
+ console.print(" • Linux: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash")
2157
+ console.print(" • Visit: https://helm.sh/docs/intro/install/")
2158
+
2159
+ except Exception as e:
2160
+ console.print(f"❌ Error installing Helm: {e}")
2161
+
2162
+
1889
2163
  if __name__ == "__main__":
1890
2164
  app()
k8s_helper/core.py CHANGED
@@ -1694,6 +1694,261 @@ class K8sClient:
1694
1694
  except (subprocess.TimeoutExpired, FileNotFoundError):
1695
1695
  return False
1696
1696
 
1697
+ def _install_helm_automatically(self) -> Dict[str, Any]:
1698
+ """Automatically install Helm based on the operating system"""
1699
+ import subprocess
1700
+ import platform
1701
+ import os
1702
+ import tempfile
1703
+ import urllib.request
1704
+
1705
+ result = {
1706
+ 'success': False,
1707
+ 'method': None,
1708
+ 'error': None,
1709
+ 'message': None
1710
+ }
1711
+
1712
+ system = platform.system().lower()
1713
+ print(f"🔧 Attempting to install Helm on {system}...")
1714
+
1715
+ try:
1716
+ if system == 'windows':
1717
+ # Try to install via Chocolatey first, then Scoop, then direct download
1718
+ methods = [
1719
+ {
1720
+ 'name': 'Chocolatey',
1721
+ 'check_cmd': ['choco', '--version'],
1722
+ 'install_cmd': ['choco', 'install', 'kubernetes-helm', '-y']
1723
+ },
1724
+ {
1725
+ 'name': 'Scoop',
1726
+ 'check_cmd': ['scoop', '--version'],
1727
+ 'install_cmd': ['scoop', 'install', 'helm']
1728
+ }
1729
+ ]
1730
+
1731
+ for method in methods:
1732
+ try:
1733
+ # Check if package manager is available
1734
+ subprocess.run(method['check_cmd'], capture_output=True, check=True, timeout=10)
1735
+ print(f"📦 Installing Helm via {method['name']}...")
1736
+
1737
+ # Install Helm
1738
+ install_result = subprocess.run(
1739
+ method['install_cmd'],
1740
+ capture_output=True,
1741
+ text=True,
1742
+ timeout=300 # 5 minutes timeout
1743
+ )
1744
+
1745
+ if install_result.returncode == 0:
1746
+ result['success'] = True
1747
+ result['method'] = method['name']
1748
+ result['message'] = f"Helm installed successfully via {method['name']}"
1749
+ print(f"✅ {result['message']}")
1750
+ return result
1751
+ else:
1752
+ print(f"⚠️ {method['name']} installation failed: {install_result.stderr}")
1753
+
1754
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError):
1755
+ print(f"⚠️ {method['name']} not available")
1756
+ continue
1757
+
1758
+ # If package managers fail, try direct download
1759
+ print("📦 Attempting direct download installation...")
1760
+ result = self._install_helm_direct_download()
1761
+
1762
+ elif system == 'linux':
1763
+ # Try different Linux package managers and methods
1764
+ methods = [
1765
+ {
1766
+ 'name': 'Snap',
1767
+ 'check_cmd': ['snap', '--version'],
1768
+ 'install_cmd': ['sudo', 'snap', 'install', 'helm', '--classic']
1769
+ },
1770
+ {
1771
+ 'name': 'Script',
1772
+ 'script_url': 'https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3'
1773
+ }
1774
+ ]
1775
+
1776
+ # Try snap first
1777
+ try:
1778
+ subprocess.run(['snap', '--version'], capture_output=True, check=True, timeout=10)
1779
+ print("📦 Installing Helm via Snap...")
1780
+
1781
+ install_result = subprocess.run(
1782
+ ['sudo', 'snap', 'install', 'helm', '--classic'],
1783
+ capture_output=True,
1784
+ text=True,
1785
+ timeout=300
1786
+ )
1787
+
1788
+ if install_result.returncode == 0:
1789
+ result['success'] = True
1790
+ result['method'] = 'Snap'
1791
+ result['message'] = "Helm installed successfully via Snap"
1792
+ print(f"✅ {result['message']}")
1793
+ return result
1794
+
1795
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError):
1796
+ print("⚠️ Snap not available")
1797
+
1798
+ # Try installation script
1799
+ print("📦 Installing Helm via official installation script...")
1800
+ try:
1801
+ script_result = subprocess.run([
1802
+ 'bash', '-c',
1803
+ 'curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash'
1804
+ ], capture_output=True, text=True, timeout=300)
1805
+
1806
+ if script_result.returncode == 0:
1807
+ result['success'] = True
1808
+ result['method'] = 'Installation Script'
1809
+ result['message'] = "Helm installed successfully via installation script"
1810
+ print(f"✅ {result['message']}")
1811
+ return result
1812
+ else:
1813
+ print(f"⚠️ Script installation failed: {script_result.stderr}")
1814
+
1815
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
1816
+ print("⚠️ Script installation failed")
1817
+
1818
+ elif system == 'darwin': # macOS
1819
+ # Try Homebrew
1820
+ try:
1821
+ subprocess.run(['brew', '--version'], capture_output=True, check=True, timeout=10)
1822
+ print("📦 Installing Helm via Homebrew...")
1823
+
1824
+ install_result = subprocess.run(
1825
+ ['brew', 'install', 'helm'],
1826
+ capture_output=True,
1827
+ text=True,
1828
+ timeout=300
1829
+ )
1830
+
1831
+ if install_result.returncode == 0:
1832
+ result['success'] = True
1833
+ result['method'] = 'Homebrew'
1834
+ result['message'] = "Helm installed successfully via Homebrew"
1835
+ print(f"✅ {result['message']}")
1836
+ return result
1837
+
1838
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError):
1839
+ print("⚠️ Homebrew not available, trying installation script...")
1840
+
1841
+ # Fallback to installation script
1842
+ try:
1843
+ script_result = subprocess.run([
1844
+ 'bash', '-c',
1845
+ 'curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash'
1846
+ ], capture_output=True, text=True, timeout=300)
1847
+
1848
+ if script_result.returncode == 0:
1849
+ result['success'] = True
1850
+ result['method'] = 'Installation Script'
1851
+ result['message'] = "Helm installed successfully via installation script"
1852
+ print(f"✅ {result['message']}")
1853
+ return result
1854
+
1855
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
1856
+ print("⚠️ Script installation failed")
1857
+
1858
+ # If we reach here, all methods failed
1859
+ result['error'] = f"Could not install Helm automatically on {system}"
1860
+ print(f"❌ {result['error']}")
1861
+
1862
+ except Exception as e:
1863
+ result['error'] = f"Error during Helm installation: {str(e)}"
1864
+ print(f"❌ {result['error']}")
1865
+
1866
+ return result
1867
+
1868
+ def _install_helm_direct_download(self) -> Dict[str, Any]:
1869
+ """Install Helm via direct download (Windows fallback)"""
1870
+ import subprocess
1871
+ import tempfile
1872
+ import zipfile
1873
+ import urllib.request
1874
+ import os
1875
+ import platform
1876
+
1877
+ result = {
1878
+ 'success': False,
1879
+ 'method': 'Direct Download',
1880
+ 'error': None,
1881
+ 'message': None
1882
+ }
1883
+
1884
+ try:
1885
+ # Determine architecture
1886
+ arch = platform.machine().lower()
1887
+ if arch == 'amd64' or arch == 'x86_64':
1888
+ arch = 'amd64'
1889
+ elif arch == 'arm64':
1890
+ arch = 'arm64'
1891
+ else:
1892
+ arch = 'amd64' # Default fallback
1893
+
1894
+ # Download URL for latest Helm
1895
+ download_url = f"https://get.helm.sh/helm-v3.13.0-windows-{arch}.zip"
1896
+
1897
+ with tempfile.TemporaryDirectory() as temp_dir:
1898
+ zip_path = os.path.join(temp_dir, "helm.zip")
1899
+
1900
+ print(f"📥 Downloading Helm from {download_url}...")
1901
+ urllib.request.urlretrieve(download_url, zip_path)
1902
+
1903
+ print("📦 Extracting Helm...")
1904
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
1905
+ zip_ref.extractall(temp_dir)
1906
+
1907
+ # Find helm.exe
1908
+ helm_exe = None
1909
+ for root, dirs, files in os.walk(temp_dir):
1910
+ if 'helm.exe' in files:
1911
+ helm_exe = os.path.join(root, 'helm.exe')
1912
+ break
1913
+
1914
+ if helm_exe:
1915
+ # Try to move to a directory in PATH
1916
+ target_locations = [
1917
+ os.path.expanduser("~/bin"),
1918
+ "C:\\Program Files\\Helm",
1919
+ os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Microsoft', 'WindowsApps')
1920
+ ]
1921
+
1922
+ for target_dir in target_locations:
1923
+ try:
1924
+ os.makedirs(target_dir, exist_ok=True)
1925
+ target_path = os.path.join(target_dir, 'helm.exe')
1926
+
1927
+ import shutil
1928
+ shutil.copy2(helm_exe, target_path)
1929
+
1930
+ # Test if it works
1931
+ test_result = subprocess.run([target_path, 'version'],
1932
+ capture_output=True, timeout=10)
1933
+ if test_result.returncode == 0:
1934
+ result['success'] = True
1935
+ result['message'] = f"Helm installed to {target_path}"
1936
+ print(f"✅ {result['message']}")
1937
+ print(f"💡 Please add {target_dir} to your PATH if not already present")
1938
+ return result
1939
+
1940
+ except (OSError, subprocess.TimeoutExpired):
1941
+ continue
1942
+
1943
+ result['error'] = "Downloaded Helm but could not install to PATH"
1944
+ else:
1945
+ result['error'] = "Could not find helm.exe in downloaded archive"
1946
+
1947
+ except Exception as e:
1948
+ result['error'] = f"Direct download failed: {str(e)}"
1949
+
1950
+ return result
1951
+
1697
1952
  def _install_kube_state_metrics(self, namespace: str) -> Dict[str, Any]:
1698
1953
  """Install kube-state-metrics using Helm if available, or manual YAML if not"""
1699
1954
  import subprocess
@@ -3083,3 +3338,413 @@ scrape_configs:
3083
3338
  except Exception as e:
3084
3339
  print(f"⚠️ Could not restart Prometheus deployment: {e}")
3085
3340
  return False
3341
+
3342
+ # ======================
3343
+ # HELM-BASED MONITORING METHODS
3344
+ # ======================
3345
+
3346
+ def setup_helm_monitoring(self, namespace: str = "monitoring",
3347
+ grafana_service_type: str = "NodePort",
3348
+ prometheus_storage_size: str = "10Gi",
3349
+ grafana_storage_size: str = "5Gi",
3350
+ wait_for_ready: bool = True,
3351
+ install_ingress: bool = False) -> Dict:
3352
+ """Deploy monitoring stack using official Helm charts"""
3353
+ import subprocess
3354
+ import tempfile
3355
+ import os
3356
+
3357
+ try:
3358
+ # Check if Helm is available, and install if not
3359
+ if not self._check_helm_available():
3360
+ print("🔧 Helm not found. Attempting automatic installation...")
3361
+ install_result = self._install_helm_automatically()
3362
+
3363
+ if not install_result['success']:
3364
+ return {
3365
+ 'success': False,
3366
+ 'error': f"Helm is required but could not be installed automatically. {install_result.get('error', '')}",
3367
+ 'suggestion': 'Please install Helm manually: https://helm.sh/docs/intro/install/'
3368
+ }
3369
+ else:
3370
+ print(f"✅ {install_result['message']}")
3371
+
3372
+ # Verify installation worked
3373
+ if not self._check_helm_available():
3374
+ return {
3375
+ 'success': False,
3376
+ 'error': 'Helm was installed but is not accessible. You may need to restart your terminal or add Helm to your PATH.',
3377
+ 'suggestion': 'Please restart your terminal and try again, or install Helm manually.'
3378
+ }
3379
+
3380
+ print("✅ Helm is available")
3381
+
3382
+ # Create namespace if it doesn't exist
3383
+ try:
3384
+ self.core_v1.create_namespace(
3385
+ body=client.V1Namespace(metadata=client.V1ObjectMeta(name=namespace))
3386
+ )
3387
+ print(f"✅ Created namespace: {namespace}")
3388
+ except ApiException as e:
3389
+ if e.status == 409: # Already exists
3390
+ print(f"✅ Namespace {namespace} already exists")
3391
+ else:
3392
+ print(f"⚠️ Could not create namespace: {e}")
3393
+
3394
+ # Add Prometheus community Helm repository
3395
+ print("📦 Adding Prometheus community Helm repository...")
3396
+ try:
3397
+ subprocess.run([
3398
+ 'helm', 'repo', 'add', 'prometheus-community',
3399
+ 'https://prometheus-community.github.io/helm-charts'
3400
+ ], check=True, capture_output=True)
3401
+
3402
+ subprocess.run(['helm', 'repo', 'update'], check=True, capture_output=True)
3403
+ print("✅ Helm repository added and updated")
3404
+ except subprocess.CalledProcessError as e:
3405
+ return {
3406
+ 'success': False,
3407
+ 'error': f'Failed to add Helm repository: {e.stderr.decode() if e.stderr else str(e)}'
3408
+ }
3409
+
3410
+ # Create Helm values file
3411
+ helm_values = {
3412
+ 'grafana': {
3413
+ 'enabled': True,
3414
+ 'persistence': {
3415
+ 'enabled': True,
3416
+ 'size': grafana_storage_size
3417
+ },
3418
+ 'service': {
3419
+ 'type': grafana_service_type
3420
+ },
3421
+ 'adminPassword': 'admin',
3422
+ 'datasources': {
3423
+ 'datasources.yaml': {
3424
+ 'apiVersion': 1,
3425
+ 'datasources': [{
3426
+ 'name': 'Prometheus',
3427
+ 'type': 'prometheus',
3428
+ 'url': 'http://kube-prometheus-stack-prometheus:9090',
3429
+ 'access': 'proxy',
3430
+ 'isDefault': True
3431
+ }]
3432
+ }
3433
+ },
3434
+ 'dashboardProviders': {
3435
+ 'dashboardproviders.yaml': {
3436
+ 'apiVersion': 1,
3437
+ 'providers': [{
3438
+ 'name': 'default',
3439
+ 'orgId': 1,
3440
+ 'folder': '',
3441
+ 'type': 'file',
3442
+ 'disableDeletion': False,
3443
+ 'editable': True,
3444
+ 'options': {
3445
+ 'path': '/var/lib/grafana/dashboards/default'
3446
+ }
3447
+ }]
3448
+ }
3449
+ },
3450
+ 'dashboards': {
3451
+ 'default': {
3452
+ 'kubernetes-cluster-dashboard': {
3453
+ 'gnetId': 7249,
3454
+ 'revision': 1,
3455
+ 'datasource': 'Prometheus'
3456
+ },
3457
+ 'kubernetes-pod-dashboard': {
3458
+ 'gnetId': 6417,
3459
+ 'revision': 1,
3460
+ 'datasource': 'Prometheus'
3461
+ },
3462
+ 'node-exporter-dashboard': {
3463
+ 'gnetId': 1860,
3464
+ 'revision': 27,
3465
+ 'datasource': 'Prometheus'
3466
+ }
3467
+ }
3468
+ }
3469
+ },
3470
+ 'prometheus': {
3471
+ 'enabled': True,
3472
+ 'prometheusSpec': {
3473
+ 'retention': '30d',
3474
+ 'storageSpec': {
3475
+ 'volumeClaimTemplate': {
3476
+ 'spec': {
3477
+ 'accessModes': ['ReadWriteOnce'],
3478
+ 'resources': {
3479
+ 'requests': {
3480
+ 'storage': prometheus_storage_size
3481
+ }
3482
+ }
3483
+ }
3484
+ }
3485
+ },
3486
+ 'serviceMonitorSelectorNilUsesHelmValues': False
3487
+ }
3488
+ },
3489
+ 'alertmanager': {
3490
+ 'enabled': True
3491
+ },
3492
+ 'nodeExporter': {
3493
+ 'enabled': True
3494
+ },
3495
+ 'kubeStateMetrics': {
3496
+ 'enabled': True
3497
+ },
3498
+ 'defaultRules': {
3499
+ 'create': True,
3500
+ 'rules': {
3501
+ 'alertmanager': True,
3502
+ 'etcd': True,
3503
+ 'general': True,
3504
+ 'k8s': True,
3505
+ 'kubeApiserver': True,
3506
+ 'kubePrometheusNodeRecording': True,
3507
+ 'kubernetesApps': True,
3508
+ 'kubernetesResources': True,
3509
+ 'kubernetesStorage': True,
3510
+ 'kubernetesSystem': True,
3511
+ 'network': True,
3512
+ 'node': True,
3513
+ 'prometheus': True,
3514
+ 'prometheusOperator': True
3515
+ }
3516
+ }
3517
+ }
3518
+
3519
+ # Add ingress if requested
3520
+ if install_ingress:
3521
+ helm_values['grafana']['ingress'] = {
3522
+ 'enabled': True,
3523
+ 'hosts': [f'grafana.{namespace}.local'],
3524
+ 'paths': ['/']
3525
+ }
3526
+
3527
+ # Write values to temporary file
3528
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
3529
+ yaml.dump(helm_values, f, default_flow_style=False)
3530
+ values_file = f.name
3531
+
3532
+ try:
3533
+ # Install the Helm chart
3534
+ print("🚀 Installing kube-prometheus-stack via Helm...")
3535
+ helm_cmd = [
3536
+ 'helm', 'install', 'kube-prometheus-stack',
3537
+ 'prometheus-community/kube-prometheus-stack',
3538
+ '--namespace', namespace,
3539
+ '--values', values_file
3540
+ ]
3541
+
3542
+ if wait_for_ready:
3543
+ helm_cmd.append('--wait')
3544
+ helm_cmd.extend(['--timeout', '10m'])
3545
+
3546
+ result = subprocess.run(helm_cmd, capture_output=True, text=True, check=True)
3547
+ print("✅ Helm chart installed successfully")
3548
+
3549
+ # Wait a bit for pods to start
3550
+ if wait_for_ready:
3551
+ print("⏳ Waiting for pods to be ready...")
3552
+ time.sleep(30)
3553
+
3554
+ # Get service information
3555
+ services_info = self._get_helm_monitoring_services(namespace)
3556
+
3557
+ return {
3558
+ 'success': True,
3559
+ 'namespace': namespace,
3560
+ 'release_name': 'kube-prometheus-stack',
3561
+ 'prometheus': {'deployed': True},
3562
+ 'grafana': {
3563
+ 'deployed': True,
3564
+ 'admin_password': 'admin'
3565
+ },
3566
+ 'grafana_url': services_info.get('grafana_url'),
3567
+ 'prometheus_url': services_info.get('prometheus_url'),
3568
+ 'alertmanager_url': services_info.get('alertmanager_url')
3569
+ }
3570
+
3571
+ finally:
3572
+ # Clean up temporary file
3573
+ os.unlink(values_file)
3574
+
3575
+ except subprocess.CalledProcessError as e:
3576
+ error_msg = e.stderr.decode() if e.stderr else str(e)
3577
+ return {
3578
+ 'success': False,
3579
+ 'error': f'Helm installation failed: {error_msg}'
3580
+ }
3581
+ except Exception as e:
3582
+ return {
3583
+ 'success': False,
3584
+ 'error': f'Failed to setup Helm monitoring: {str(e)}'
3585
+ }
3586
+
3587
+ def get_helm_monitoring_info(self, namespace: str = "monitoring") -> Dict:
3588
+ """Get information about the Helm-based monitoring stack"""
3589
+ import subprocess
3590
+
3591
+ try:
3592
+ # Check if Helm release exists
3593
+ try:
3594
+ result = subprocess.run([
3595
+ 'helm', 'status', 'kube-prometheus-stack',
3596
+ '--namespace', namespace
3597
+ ], capture_output=True, text=True, check=True)
3598
+
3599
+ # Parse Helm status
3600
+ lines = result.stdout.split('\n')
3601
+ release_info = {}
3602
+ for line in lines:
3603
+ if 'STATUS:' in line:
3604
+ release_info['release_status'] = line.split('STATUS:')[1].strip()
3605
+ elif 'LAST DEPLOYED:' in line:
3606
+ release_info['last_deployed'] = line.split('LAST DEPLOYED:')[1].strip()
3607
+
3608
+ except subprocess.CalledProcessError:
3609
+ return {'error': 'Helm release not found. Use setup-helm-monitoring to deploy first.'}
3610
+
3611
+ # Get services information
3612
+ services_info = self._get_helm_monitoring_services(namespace)
3613
+
3614
+ # Get pod status
3615
+ pods_info = self._get_monitoring_pods_status(namespace)
3616
+
3617
+ return {
3618
+ 'release_name': 'kube-prometheus-stack',
3619
+ 'release_status': release_info.get('release_status', 'Unknown'),
3620
+ 'last_deployed': release_info.get('last_deployed', 'Unknown'),
3621
+ 'prometheus': {
3622
+ 'running': any(pod['name'].startswith('prometheus-kube-prometheus-stack-prometheus')
3623
+ for pod in pods_info if pod['status'] == 'Running'),
3624
+ 'url': services_info.get('prometheus_url', 'Port-forward required')
3625
+ },
3626
+ 'grafana': {
3627
+ 'running': any(pod['name'].startswith('kube-prometheus-stack-grafana')
3628
+ for pod in pods_info if pod['status'] == 'Running'),
3629
+ 'url': services_info.get('grafana_url', 'Port-forward required')
3630
+ },
3631
+ 'alertmanager': {
3632
+ 'running': any(pod['name'].startswith('alertmanager-kube-prometheus-stack-alertmanager')
3633
+ for pod in pods_info if pod['status'] == 'Running'),
3634
+ 'url': services_info.get('alertmanager_url', 'Port-forward required')
3635
+ },
3636
+ 'pods': pods_info
3637
+ }
3638
+
3639
+ except Exception as e:
3640
+ return {'error': f'Failed to get monitoring info: {str(e)}'}
3641
+
3642
+ def delete_helm_monitoring(self, namespace: str = "monitoring",
3643
+ release_name: str = "kube-prometheus-stack") -> Dict:
3644
+ """Delete Helm-based monitoring stack"""
3645
+ import subprocess
3646
+
3647
+ try:
3648
+ # Uninstall Helm release
3649
+ result = subprocess.run([
3650
+ 'helm', 'uninstall', release_name,
3651
+ '--namespace', namespace
3652
+ ], capture_output=True, text=True, check=True)
3653
+
3654
+ print(f"✅ Helm release '{release_name}' uninstalled")
3655
+
3656
+ # Count remaining resources (optional cleanup)
3657
+ try:
3658
+ # Delete PVCs that might remain
3659
+ pvcs = self.core_v1.list_namespaced_persistent_volume_claim(namespace=namespace)
3660
+ pvc_count = 0
3661
+ for pvc in pvcs.items:
3662
+ if 'prometheus' in pvc.metadata.name or 'grafana' in pvc.metadata.name:
3663
+ self.core_v1.delete_namespaced_persistent_volume_claim(
3664
+ name=pvc.metadata.name,
3665
+ namespace=namespace
3666
+ )
3667
+ pvc_count += 1
3668
+
3669
+ if pvc_count > 0:
3670
+ print(f"✅ Cleaned up {pvc_count} persistent volume claims")
3671
+
3672
+ except Exception as cleanup_error:
3673
+ print(f"⚠️ Could not clean up some resources: {cleanup_error}")
3674
+
3675
+ return {
3676
+ 'success': True,
3677
+ 'resources_deleted': pvc_count
3678
+ }
3679
+
3680
+ except subprocess.CalledProcessError as e:
3681
+ error_msg = e.stderr.decode() if e.stderr else str(e)
3682
+ return {
3683
+ 'success': False,
3684
+ 'error': f'Failed to uninstall Helm release: {error_msg}'
3685
+ }
3686
+ except Exception as e:
3687
+ return {
3688
+ 'success': False,
3689
+ 'error': f'Failed to delete monitoring stack: {str(e)}'
3690
+ }
3691
+
3692
+ def _get_helm_monitoring_services(self, namespace: str) -> Dict:
3693
+ """Get service URLs for Helm monitoring components"""
3694
+ services_info = {}
3695
+
3696
+ try:
3697
+ # Get services
3698
+ services = self.core_v1.list_namespaced_service(namespace=namespace)
3699
+
3700
+ for service in services.items:
3701
+ service_name = service.metadata.name
3702
+
3703
+ if 'grafana' in service_name:
3704
+ url = self._get_service_url(service, namespace, 80)
3705
+ if url:
3706
+ services_info['grafana_url'] = url
3707
+
3708
+ elif 'prometheus' in service_name and 'operated' not in service_name:
3709
+ url = self._get_service_url(service, namespace, 9090)
3710
+ if url:
3711
+ services_info['prometheus_url'] = url
3712
+
3713
+ elif 'alertmanager' in service_name and 'operated' not in service_name:
3714
+ url = self._get_service_url(service, namespace, 9093)
3715
+ if url:
3716
+ services_info['alertmanager_url'] = url
3717
+
3718
+ except Exception as e:
3719
+ print(f"⚠️ Could not get service information: {e}")
3720
+
3721
+ return services_info
3722
+
3723
+ def _get_monitoring_pods_status(self, namespace: str) -> List[Dict]:
3724
+ """Get status of monitoring pods"""
3725
+ pods_info = []
3726
+
3727
+ try:
3728
+ pods = self.core_v1.list_namespaced_pod(namespace=namespace)
3729
+
3730
+ for pod in pods.items:
3731
+ if any(component in pod.metadata.name for component in
3732
+ ['prometheus', 'grafana', 'alertmanager', 'node-exporter', 'kube-state-metrics']):
3733
+
3734
+ ready_containers = 0
3735
+ total_containers = len(pod.status.container_statuses) if pod.status.container_statuses else 0
3736
+
3737
+ if pod.status.container_statuses:
3738
+ ready_containers = sum(1 for cs in pod.status.container_statuses if cs.ready)
3739
+
3740
+ pods_info.append({
3741
+ 'name': pod.metadata.name,
3742
+ 'status': pod.status.phase,
3743
+ 'ready': ready_containers,
3744
+ 'total': total_containers
3745
+ })
3746
+
3747
+ except Exception as e:
3748
+ print(f"⚠️ Could not get pod status: {e}")
3749
+
3750
+ return pods_info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: k8s-helper-cli
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: A simplified Python wrapper for common Kubernetes operations
5
5
  Author-email: Harshit Chatterjee <harshitchatterjee50@gmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1,11 @@
1
+ k8s_helper/__init__.py,sha256=6P0gvzwSbX4EBvqjSs8DO6uNsGzJAFG36qUqVY9vlnQ,2666
2
+ k8s_helper/cli.py,sha256=3eP3J7srPIM0bm_G5RxdU_RvtDwJHLX2xBguF2EmXtk,95115
3
+ k8s_helper/config.py,sha256=P7YdfyvCHprrNs2J9DRb3RrClylfTTh5hfTtDzLug0A,6867
4
+ k8s_helper/core.py,sha256=WNri9uYRPdU_NRCdEpkgJhxzAC3iKaF2UntALvZS4ng,160448
5
+ k8s_helper/utils.py,sha256=wYgTd5ktyuI-EiVcfW7FrxA7MzXY5odrEKQgmMVdueY,9496
6
+ k8s_helper_cli-0.5.2.dist-info/licenses/LICENSE,sha256=tXPvVl3gLVc6e0qCEoLH9KjeA7z4JVL78UybpvGtBCw,1096
7
+ k8s_helper_cli-0.5.2.dist-info/METADATA,sha256=1G1nZLtXZPaYsaCr4pyjRWqor80TUNa0DnQhSriSZSo,30789
8
+ k8s_helper_cli-0.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ k8s_helper_cli-0.5.2.dist-info/entry_points.txt,sha256=IoCMWUZ6mn90LwzQzEy5YkWOwvogDdZ6ycqUWAzCFTQ,50
10
+ k8s_helper_cli-0.5.2.dist-info/top_level.txt,sha256=x9A1jflyer-z2cFnkqk5B42juoH2q0fy5hkT9upsTG8,11
11
+ k8s_helper_cli-0.5.2.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- k8s_helper/__init__.py,sha256=wtG9p8ZbOO4WDPZxf3acQ_pNu4x92MJBnwI9l8G7LCc,2666
2
- k8s_helper/cli.py,sha256=dBqPBfSNoh3NAFq5qiyERD0uV6Entf-rEwpuBhuYHGk,81655
3
- k8s_helper/config.py,sha256=P7YdfyvCHprrNs2J9DRb3RrClylfTTh5hfTtDzLug0A,6867
4
- k8s_helper/core.py,sha256=rf4dwcrV9fTyRZrWTucslOHJDTKhlnMXKqV_OWTIYQ8,130172
5
- k8s_helper/utils.py,sha256=wYgTd5ktyuI-EiVcfW7FrxA7MzXY5odrEKQgmMVdueY,9496
6
- k8s_helper_cli-0.5.0.dist-info/licenses/LICENSE,sha256=tXPvVl3gLVc6e0qCEoLH9KjeA7z4JVL78UybpvGtBCw,1096
7
- k8s_helper_cli-0.5.0.dist-info/METADATA,sha256=dHNcGQ7c-7KH9Z98yj4DFcXphLjDtQtoAt0cYaxEsSc,30789
8
- k8s_helper_cli-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- k8s_helper_cli-0.5.0.dist-info/entry_points.txt,sha256=IoCMWUZ6mn90LwzQzEy5YkWOwvogDdZ6ycqUWAzCFTQ,50
10
- k8s_helper_cli-0.5.0.dist-info/top_level.txt,sha256=x9A1jflyer-z2cFnkqk5B42juoH2q0fy5hkT9upsTG8,11
11
- k8s_helper_cli-0.5.0.dist-info/RECORD,,