9494)
9595
9696from cabotage .celery .tasks .deploy import scale_deployment
97+ from cabotage .utils .build_log_stream import (
98+ get_redis_client ,
99+ read_log_stream ,
100+ stream_key ,
101+ )
97102
98103Activity = activity_plugin .activity_cls
99104user_blueprint = Blueprint (
@@ -959,30 +964,12 @@ def image_detail(image_id):
959964 )
960965
961966
962- @sock .route ("/image/<image_id>/livelogs" , bp = user_blueprint )
963- @login_required
964- def image_build_livelogs (ws , image_id ):
965- image = Image .query .filter_by (id = image_id ).first_or_404 ()
966- if image .error :
967- abort (404 )
968- if not ViewApplicationPermission (image .application .id ).can ():
969- abort (403 )
970- build_job_id = image .build_job_id
971- image_build_log = image .image_build_log
972- if image_build_log is not None :
973- ws .send (f"Job Pod imagebuild-{ build_job_id } " )
974- for line in image_build_log .split ("\n " ):
975- ws .send (f" { line } " )
976- ws .send ("=================END OF LOGS=================" )
977-
978- db .session .remove ()
979-
967+ def _stream_k8s_job_logs (ws , job_name , namespace , poll_interval = 1 ):
968+ """Stream logs from a Kubernetes job pod over a WebSocket."""
980969 api_client = kubernetes_ext .kubernetes_client
981970 core_api_instance = kubernetes .client .CoreV1Api (api_client )
982971 batch_api_instance = kubernetes .client .BatchV1Api (api_client )
983972
984- job_name , namespace = (f"imagebuild-{ build_job_id } " , "default" )
985-
986973 @backoff .on_exception (
987974 backoff .constant ,
988975 kubernetes .client .exceptions .ApiException ,
@@ -1008,22 +995,28 @@ def fetch_job_object():
1008995 except kubernetes .client .exceptions .ApiException as exc :
1009996 ws .send ("=================END OF LOGS=================" )
1010997 print (f"Encountered exception: { exc } " )
1011- return False
998+ return
1012999
10131000 if len (pods .items ) != 1 :
10141001 ws .send ("=================END OF LOGS=================" )
10151002 print ("Found too many pods!" )
1016- return False
1003+ return
10171004
10181005 pod = pods .items [0 ]
10191006 while True :
1020- pod = core_api_instance .read_namespaced_pod (
1021- pod .metadata .name , pod .metadata .namespace
1022- )
1023- if pod .status .phase == "Running" :
1024- break
1025- time .sleep (1 )
1026- ws .send (f"Job Pod imagebuild-{ build_job_id } " )
1007+ try :
1008+ pod = core_api_instance .read_namespaced_pod (
1009+ pod .metadata .name , pod .metadata .namespace
1010+ )
1011+ if pod .status .phase == "Running" :
1012+ break
1013+ time .sleep (poll_interval )
1014+ except kubernetes .client .exceptions .ApiException as exc :
1015+ ws .send ("=================END OF LOGS=================" )
1016+ print (f"Encountered exception: { exc } " )
1017+ return
1018+
1019+ ws .send (f"Job Pod { job_name } " )
10271020 w = kubernetes .watch .Watch ()
10281021 for line in w .stream (
10291022 core_api_instance .read_namespaced_pod_log ,
@@ -1039,6 +1032,53 @@ def fetch_job_object():
10391032 ws .send ("=================END OF LOGS=================" )
10401033
10411034
1035+ def _stream_redis_build_logs (ws , build_type , build_job_id , job_label ):
1036+ """Stream build logs from Redis over a WebSocket."""
1037+ redis_client = get_redis_client (current_app .config ["CELERY_BROKER_URL" ])
1038+ log_key = stream_key (build_type , build_job_id )
1039+ ws .send (f"Job Pod { job_label } " )
1040+ for line in read_log_stream (redis_client , log_key ):
1041+ if line is None :
1042+ continue
1043+ ws .send (f" { line } " )
1044+ ws .send ("=================END OF LOGS=================" )
1045+
1046+
1047+ @sock .route ("/image/<image_id>/livelogs" , bp = user_blueprint )
1048+ @login_required
1049+ def image_build_livelogs (ws , image_id ):
1050+ image = Image .query .filter_by (id = image_id ).first_or_404 ()
1051+ if image .error :
1052+ abort (404 )
1053+ if not ViewApplicationPermission (image .application .id ).can ():
1054+ abort (403 )
1055+ build_job_id = image .build_job_id
1056+ image_build_log = image .image_build_log
1057+
1058+ if image_build_log is not None :
1059+ ws .send (f"Job Pod imagebuild-{ build_job_id } " )
1060+ for line in image_build_log .split ("\n " ):
1061+ ws .send (f" { line } " )
1062+ ws .send ("=================END OF LOGS=================" )
1063+ return
1064+
1065+ db .session .remove ()
1066+
1067+ if current_app .config ["KUBERNETES_ENABLED" ]:
1068+ _stream_k8s_job_logs (
1069+ ws ,
1070+ f"imagebuild-{ build_job_id } " ,
1071+ "default" ,
1072+ )
1073+ else :
1074+ _stream_redis_build_logs (
1075+ ws ,
1076+ "image" ,
1077+ build_job_id ,
1078+ f"imagebuild-{ build_job_id } " ,
1079+ )
1080+
1081+
10421082@user_blueprint .route ("/applications/<application_id>/releases" )
10431083@login_required
10441084def application_releases (application_id ):
@@ -1090,74 +1130,29 @@ def release_build_livelogs(ws, release_id):
10901130 abort (403 )
10911131 build_job_id = release .build_job_id
10921132 release_build_log = release .release_build_log
1133+
10931134 if release_build_log is not None :
10941135 ws .send (f"Job Pod releasebuild-{ build_job_id } " )
10951136 for line in release_build_log .split ("\n " ):
10961137 ws .send (f" { line } " )
10971138 ws .send ("=================END OF LOGS=================" )
1139+ return
10981140
10991141 db .session .remove ()
11001142
1101- api_client = kubernetes_ext .kubernetes_client
1102- core_api_instance = kubernetes .client .CoreV1Api (api_client )
1103- batch_api_instance = kubernetes .client .BatchV1Api (api_client )
1104-
1105- job_name , namespace = (f"releasebuild-{ build_job_id } " , "default" )
1106-
1107- @backoff .on_exception (
1108- backoff .constant ,
1109- kubernetes .client .exceptions .ApiException ,
1110- max_tries = 20 ,
1111- interval = 0.25 ,
1112- )
1113- def fetch_job_object ():
1114- return batch_api_instance .read_namespaced_job (job_name , namespace )
1115-
1116- try :
1117- job_object = fetch_job_object ()
1118- except kubernetes .client .exceptions .ApiException :
1119- ws .send ("=================END OF LOGS=================" )
1120- return
1121-
1122- label_selector = "," .join (
1123- [f"{ k } ={ v } " for k , v in job_object .spec .template .metadata .labels .items ()]
1124- )
1125- try :
1126- pods = core_api_instance .list_namespaced_pod (
1127- namespace , label_selector = label_selector
1143+ if current_app .config ["KUBERNETES_ENABLED" ]:
1144+ _stream_k8s_job_logs (
1145+ ws ,
1146+ f"releasebuild-{ build_job_id } " ,
1147+ "default" ,
11281148 )
1129- except kubernetes .client .exceptions .ApiException as exc :
1130- ws .send ("=================END OF LOGS=================" )
1131- print (f"Encountered exception: { exc } " )
1132- return False
1133-
1134- if len (pods .items ) != 1 :
1135- ws .send ("=================END OF LOGS=================" )
1136- print ("Found too many pods!" )
1137- return False
1138-
1139- pod = pods .items [0 ]
1140- while True :
1141- pod = core_api_instance .read_namespaced_pod (
1142- pod .metadata .name , pod .metadata .namespace
1149+ else :
1150+ _stream_redis_build_logs (
1151+ ws ,
1152+ "release" ,
1153+ build_job_id ,
1154+ f"releasebuild-{ build_job_id } " ,
11431155 )
1144- if pod .status .phase == "Running" :
1145- break
1146- time .sleep (1 )
1147- ws .send (f"Job Pod releasebuild-{ build_job_id } " )
1148- w = kubernetes .watch .Watch ()
1149- for line in w .stream (
1150- core_api_instance .read_namespaced_pod_log ,
1151- name = pod .metadata .name ,
1152- namespace = namespace ,
1153- container = job_object .metadata .labels ["process" ],
1154- follow = True ,
1155- _preload_content = False ,
1156- pretty = "true" ,
1157- ):
1158- ws .send (f" { line } " )
1159-
1160- ws .send ("=================END OF LOGS=================" )
11611156
11621157
11631158@sock .route ("/deployment/<deployment_id>/livelogs" , bp = user_blueprint )
@@ -1175,75 +1170,20 @@ def deployment_livelogs(ws, deployment_id):
11751170 for line in deploy_log .split ("\n " ):
11761171 ws .send (f" { line } " )
11771172 ws .send ("=================END OF LOGS=================" )
1173+ return
11781174
11791175 db .session .remove ()
11801176
1181- api_client = kubernetes_ext .kubernetes_client
1182- core_api_instance = kubernetes .client .CoreV1Api (api_client )
1183- batch_api_instance = kubernetes .client .BatchV1Api (api_client )
1184-
1185- job_name = f"deployment-{ job_id } "
1186-
1187- @backoff .on_exception (
1188- backoff .constant ,
1189- kubernetes .client .exceptions .ApiException ,
1190- max_tries = 20 ,
1191- interval = 0.25 ,
1192- )
1193- def fetch_job_object ():
1194- return batch_api_instance .read_namespaced_job (job_name , namespace )
1195-
1196- try :
1197- job_object = fetch_job_object ()
1198- except kubernetes .client .exceptions .ApiException :
1199- ws .send ("=================END OF LOGS=================" )
1200- return
1201-
1202- label_selector = "," .join (
1203- [f"{ k } ={ v } " for k , v in job_object .spec .template .metadata .labels .items ()]
1204- )
1205- try :
1206- pods = core_api_instance .list_namespaced_pod (
1207- namespace , label_selector = label_selector
1177+ if current_app .config ["KUBERNETES_ENABLED" ]:
1178+ _stream_k8s_job_logs (
1179+ ws ,
1180+ f"deployment-{ job_id } " ,
1181+ namespace ,
1182+ poll_interval = 0.25 ,
12081183 )
1209- except kubernetes .client .exceptions .ApiException as exc :
1210- ws .send ("=================END OF LOGS=================" )
1211- print (f"Encountered exception: { exc } " )
1212- return False
1213-
1214- if len (pods .items ) != 1 :
1184+ else :
1185+ # deploy runs synchronously, log should already be in DB
12151186 ws .send ("=================END OF LOGS=================" )
1216- print ("Found too many pods!" )
1217- return False
1218-
1219- pod = pods .items [0 ]
1220- while True :
1221- try :
1222- pod = core_api_instance .read_namespaced_pod (
1223- pod .metadata .name , pod .metadata .namespace
1224- )
1225- if pod .status .phase == "Running" :
1226- break
1227- time .sleep (0.25 )
1228- except kubernetes .client .exceptions .ApiException as exc :
1229- ws .send ("=================END OF LOGS=================" )
1230- print (f"Encountered exception: { exc } " )
1231- return False
1232-
1233- ws .send (f"Job Pod deployment-{ job_id } " )
1234- w = kubernetes .watch .Watch ()
1235- for line in w .stream (
1236- core_api_instance .read_namespaced_pod_log ,
1237- name = pod .metadata .name ,
1238- namespace = namespace ,
1239- container = job_object .metadata .labels ["process" ],
1240- follow = True ,
1241- _preload_content = False ,
1242- pretty = "true" ,
1243- ):
1244- ws .send (f" { line } " )
1245-
1246- ws .send ("=================END OF LOGS=================" )
12471187
12481188
12491189@user_blueprint .route ("/deployment/<deployment_id>" )
0 commit comments