#!/usr/bin/env python3 """ Detect configuration drift between Git and Kubernetes cluster. Supports both ArgoCD and Flux CD deployments. """ import argparse import sys import subprocess import json from typing import Dict, List, Optional try: from kubernetes import client, config except ImportError: print("āš ļø 'kubernetes' library not found. Install with: pip install kubernetes") sys.exit(1) try: import yaml except ImportError: print("āš ļø 'pyyaml' library not found. Install with: pip install pyyaml") sys.exit(1) def run_command(cmd: List[str]) -> tuple: """Run shell command and return output.""" try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) return result.stdout, None except subprocess.CalledProcessError as e: return None, e.stderr def check_argocd_drift(app_name: Optional[str] = None): """Check drift using ArgoCD CLI.""" print("šŸ” Checking ArgoCD drift...\n") cmd = ['argocd', 'app', 'diff'] if app_name: cmd.append(app_name) else: # Get all apps stdout, err = run_command(['argocd', 'app', 'list', '-o', 'json']) if err: print(f"āŒ Failed to list apps: {err}") return apps = json.loads(stdout) for app in apps: app_name = app['metadata']['name'] check_single_app_drift(app_name) return check_single_app_drift(app_name) def check_single_app_drift(app_name: str): """Check drift for single ArgoCD application.""" stdout, err = run_command(['argocd', 'app', 'diff', app_name]) if err and 'no differences' not in err.lower(): print(f"āŒ {app_name}: Error checking drift") print(f" {err}") return if not stdout or 'no differences' in (stdout + (err or '')).lower(): print(f"āœ… {app_name}: No drift detected") else: print(f"āš ļø {app_name}: Drift detected") print(f" Run: argocd app sync {app_name}") def check_flux_drift(namespace: str = 'flux-system'): """Check drift using Flux CLI.""" print("šŸ” Checking Flux drift...\n") # Check kustomizations stdout, err = run_command(['flux', 'get', 'kustomizations', '-n', namespace, '--status-selector', 'ready=false']) if stdout: print("āš ļø Out-of-sync Kustomizations:") print(stdout) else: print("āœ… All Kustomizations synced") # Check helmreleases stdout, err = run_command(['flux', 'get', 'helmreleases', '-n', namespace, '--status-selector', 'ready=false']) if stdout: print("\nāš ļø Out-of-sync HelmReleases:") print(stdout) else: print("āœ… All HelmReleases synced") def main(): parser = argparse.ArgumentParser( description='Detect configuration drift between Git and cluster', epilog=""" Examples: # Check ArgoCD drift python3 sync_drift_detector.py --argocd # Check specific ArgoCD app python3 sync_drift_detector.py --argocd --app my-app # Check Flux drift python3 sync_drift_detector.py --flux Requirements: - argocd CLI (for ArgoCD mode) - flux CLI (for Flux mode) - kubectl configured """ ) parser.add_argument('--argocd', action='store_true', help='Check ArgoCD drift') parser.add_argument('--flux', action='store_true', help='Check Flux drift') parser.add_argument('--app', help='Specific ArgoCD application name') parser.add_argument('--namespace', default='flux-system', help='Flux namespace') args = parser.parse_args() if not args.argocd and not args.flux: print("āŒ Specify --argocd or --flux") sys.exit(1) try: if args.argocd: check_argocd_drift(args.app) if args.flux: check_flux_drift(args.namespace) except KeyboardInterrupt: print("\n\nInterrupted") sys.exit(1) except Exception as e: print(f"āŒ Error: {e}") sys.exit(1) if __name__ == '__main__': main()