Skip to content

Commit 142270b

Browse files
committed
Harden Jenkins stop escalation
1 parent 81275ab commit 142270b

1 file changed

Lines changed: 36 additions & 5 deletions

File tree

.github/scripts/ci_common.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def cancel_superseded(base, headers, job, match, current):
316316
tree = urllib.parse.quote("builds[number,building,actions[parameters[name,value]]]", safe="")
317317
for build in jget(base, headers, f"/{path}/api/json?tree={tree}").get("builds", []):
318318
if build.get("building") and superseded(build.get("actions"), match, current):
319-
jpost(base, headers, f"/{path}/{build['number']}/stop")
319+
stop_build(base, headers, job, build["number"])
320320
tree = urllib.parse.quote("items[id,task[fullName],actions[parameters[name,value]]]", safe="")
321321
for item in jget(base, headers, f"/queue/api/json?tree={tree}").get("items", []):
322322
if (item.get("task") or {}).get("fullName") == job and superseded(item.get("actions"), match, current):
@@ -330,7 +330,7 @@ def cancel_matching(base, headers, job, match):
330330
values = parameters(build.get("actions"))
331331
if build.get("building") and all(values.get(key) == value for key, value in match.items()):
332332
print(f"Stopping superseded Jenkins build {job} #{build['number']}.")
333-
jpost(base, headers, f"/{path}/{build['number']}/stop")
333+
stop_build(base, headers, job, build["number"])
334334
tree = urllib.parse.quote("items[id,task[fullName],actions[parameters[name,value]]]", safe="")
335335
for item in jget(base, headers, f"/queue/api/json?tree={tree}").get("items", []):
336336
values = parameters(item.get("actions"))
@@ -355,11 +355,42 @@ def start_files_job(base, headers, job, fields, files):
355355
return wait_queue(base, headers, response_headers["Location"], job)
356356

357357

358-
def stop_build(base, headers, job, number):
358+
def build_is_running(base, headers, job, number):
359+
path = job_path(job)
359360
try:
360-
jpost(base, headers, f"/{job_path(job)}/{number}/stop")
361+
return bool(jget(base, headers, f"/{path}/{number}/api/json?tree=building").get("building"))
361362
except CiError as exc:
362-
print(f"Could not stop Jenkins build {job} #{number}: {exc}")
363+
print(f"Could not query Jenkins build {job} #{number}: {exc}")
364+
return False
365+
366+
367+
def wait_build_stopped(base, headers, job, number, timeout_seconds):
368+
deadline = time.monotonic() + timeout_seconds
369+
while time.monotonic() < deadline:
370+
if not build_is_running(base, headers, job, number):
371+
return True
372+
time.sleep(2)
373+
return not build_is_running(base, headers, job, number)
374+
375+
376+
def stop_build(base, headers, job, number):
377+
path = job_path(job)
378+
if not build_is_running(base, headers, job, number):
379+
return True
380+
for endpoint, wait_seconds in [("stop", 30), ("term", 60), ("kill", 30)]:
381+
if not build_is_running(base, headers, job, number):
382+
return True
383+
try:
384+
print(f"Requesting Jenkins {endpoint} for {job} #{number}.")
385+
jpost(base, headers, f"/{path}/{number}/{endpoint}")
386+
except CiError as exc:
387+
print(f"Could not request Jenkins {endpoint} for {job} #{number}: {exc}")
388+
if wait_build_stopped(base, headers, job, number, wait_seconds):
389+
print(f"Jenkins build {job} #{number} stopped after {endpoint}.")
390+
return True
391+
if build_is_running(base, headers, job, number):
392+
raise CiError(f"Jenkins build {job} #{number} is still running after stop escalation.")
393+
return True
363394

364395

365396
def redact_console(text):

0 commit comments

Comments
 (0)