|
| 1 | +import argparse |
| 2 | +import os |
| 3 | +from kubernetes import client, config |
| 4 | +from kubernetes.client.rest import ApiException |
| 5 | +from pathlib import Path |
| 6 | +import json |
| 7 | +import sys |
| 8 | +import time |
| 9 | + |
| 10 | + |
| 11 | +def check_manifest_files(deployment_config_path: str, workspace: str): |
| 12 | + with open(deployment_config_path, "r", encoding="utf-8") as f: |
| 13 | + deployment_config = json.load(f) |
| 14 | + |
| 15 | + services = deployment_config["services"] |
| 16 | + |
| 17 | + for service in services: |
| 18 | + controller, service_name_lower, controller_lower = extract_service_info(service) |
| 19 | + |
| 20 | + manifest_path = ( |
| 21 | + Path(workspace) |
| 22 | + / f"deployments/sequencer/dist/sequencer-{service_name_lower}/{controller}.sequencer-{service_name_lower}-{controller_lower}.k8s.yaml" |
| 23 | + ) |
| 24 | + |
| 25 | + if not manifest_path.exists(): |
| 26 | + print( |
| 27 | + f"❌ Manifest {manifest_path} for {service_name_lower} not found. Aborting..." |
| 28 | + ) |
| 29 | + try: |
| 30 | + dir_listing = list(manifest_path.parent.iterdir()) |
| 31 | + print( |
| 32 | + f"Contents of {manifest_path.parent}: {[str(f) for f in dir_listing]}" |
| 33 | + ) |
| 34 | + except FileNotFoundError: |
| 35 | + print(f"(Directory {manifest_path.parent} does not exist)") |
| 36 | + sys.exit(1) |
| 37 | + |
| 38 | + |
| 39 | +def extract_service_info(service): |
| 40 | + service_name = service["name"] |
| 41 | + controller = service["controller"] |
| 42 | + return controller, service_name.lower(), controller.lower() |
| 43 | + |
| 44 | + |
| 45 | +def wait_for_services_ready(deployment_config_path: str, namespace: str): |
| 46 | + config.load_kube_config() |
| 47 | + |
| 48 | + with open(deployment_config_path, "r", encoding="utf-8") as f: |
| 49 | + deployment_config = json.load(f) |
| 50 | + |
| 51 | + services = deployment_config["services"] |
| 52 | + |
| 53 | + apps_v1 = client.AppsV1Api() |
| 54 | + |
| 55 | + for service in services: |
| 56 | + controller, service_name_lower, controller_lower = extract_service_info(service) |
| 57 | + |
| 58 | + resource_name = f"sequencer-{service_name_lower}-{controller_lower}" |
| 59 | + |
| 60 | + print(f"🔍 Checking {controller_lower}: {resource_name}") |
| 61 | + |
| 62 | + try: |
| 63 | + if controller_lower == "statefulset": |
| 64 | + obj = apps_v1.read_namespaced_stateful_set( |
| 65 | + resource_name, namespace=namespace |
| 66 | + ) |
| 67 | + elif controller_lower == "deployment": |
| 68 | + obj = apps_v1.read_namespaced_deployment( |
| 69 | + resource_name, namespace=namespace |
| 70 | + ) |
| 71 | + else: |
| 72 | + print(f"❌ Unknown controller: {controller}. Skipping...") |
| 73 | + sys.exit(1) |
| 74 | + except ApiException as e: |
| 75 | + print(f"❌ API Exception occurred: {e}") |
| 76 | + raise |
| 77 | + |
| 78 | + # Describe & status info (light equivalent of kubectl describe) |
| 79 | + print( |
| 80 | + f"🔍 {controller} {resource_name} status: replicas={obj.status.replicas}, ready={obj.status.ready_replicas}" |
| 81 | + ) |
| 82 | + |
| 83 | + print(f"⏳ Waiting for {controller_lower}/{resource_name} to become ready...") |
| 84 | + |
| 85 | + timeout_seconds = 180 |
| 86 | + poll_interval = 5 |
| 87 | + elapsed = 0 |
| 88 | + |
| 89 | + while elapsed < timeout_seconds: |
| 90 | + try: |
| 91 | + if controller_lower == "statefulset": |
| 92 | + status = apps_v1.read_namespaced_stateful_set_status( |
| 93 | + resource_name, namespace |
| 94 | + ).status |
| 95 | + ready = status.ready_replicas or 0 |
| 96 | + desired = status.replicas or 0 |
| 97 | + elif controller_lower == "deployment": |
| 98 | + status = apps_v1.read_namespaced_deployment_status( |
| 99 | + resource_name, namespace |
| 100 | + ).status |
| 101 | + ready = status.ready_replicas or 0 |
| 102 | + desired = status.replicas or 0 |
| 103 | + else: |
| 104 | + print(f"❌ Unknown controller: {controller}.") |
| 105 | + sys.exit(1) |
| 106 | + |
| 107 | + if ready == desired and ready > 0: |
| 108 | + print(f"✅ {controller} {resource_name} is ready.") |
| 109 | + break |
| 110 | + except ApiException as e: |
| 111 | + print(f"❌ Error while checking status: {e}") |
| 112 | + |
| 113 | + time.sleep(poll_interval) |
| 114 | + elapsed += poll_interval |
| 115 | + else: |
| 116 | + print( |
| 117 | + f"❌ Timeout waiting for {controller} {resource_name} to become ready." |
| 118 | + ) |
| 119 | + sys.exit(1) |
| 120 | + |
| 121 | + |
| 122 | +if __name__ == "__main__": |
| 123 | + parser = argparse.ArgumentParser( |
| 124 | + description="Check manifest files and wait for K8s services to be ready." |
| 125 | + ) |
| 126 | + parser.add_argument( |
| 127 | + "--deployment_config_path", |
| 128 | + required=True, |
| 129 | + help="Path to the deployment config JSON file", |
| 130 | + ) |
| 131 | + parser.add_argument( |
| 132 | + "--namespace", |
| 133 | + required=True, |
| 134 | + help="Kubernetes namespace", |
| 135 | + ) |
| 136 | + args = parser.parse_args() |
| 137 | + |
| 138 | + github_workspace = os.environ["GITHUB_WORKSPACE"] |
| 139 | + |
| 140 | + check_manifest_files(args.deployment_config_path, github_workspace) |
| 141 | + wait_for_services_ready(args.deployment_config_path, namespace=args.namespace) |
| 142 | + print("✅ All sequencer services are ready.") |
0 commit comments