From 357b88b1e8e54eebf0d8dab76bec546f5cc555f2 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 29 May 2026 11:13:28 -0400 Subject: [PATCH 1/4] helper script to trigger trivy scans --- devbin/trivy-scan-trigger.py | 119 +++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100755 devbin/trivy-scan-trigger.py diff --git a/devbin/trivy-scan-trigger.py b/devbin/trivy-scan-trigger.py new file mode 100755 index 00000000000..3e2e92c2207 --- /dev/null +++ b/devbin/trivy-scan-trigger.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Find the most recent completed ci/deploy/prod batch and extract the batch image name.""" +import itertools +import json +import re +import subprocess +import sys +import threading +import time + + +def run(args): + result = subprocess.run(args, capture_output=True, text=True) + if result.returncode != 0: + print(result.stderr, file=sys.stderr) + sys.exit(1) + return result.stdout + + +def print_placeholder_during_lookup(name, getter): + stop = threading.Event() + result = [None] + prefix = f'{name} : ' + + def spin(): + for frame in itertools.cycle('⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'): + if stop.is_set(): + break + print(f'\r{prefix}{frame}', end='', flush=True) + time.sleep(0.1) + + t = threading.Thread(target=spin, daemon=True) + t.start() + try: + result[0] = getter() + finally: + stop.set() + t.join() + + line = f'{prefix}{result[0]}' + print(f'\r{line:<60}') + return result[0] + + +def find_batch_id(): + batches = json.loads(run(['hailctl', 'batch', 'list', '--query', 'batch_type=ci/deploy/prod', '--limit', '10', '-o', 'json'])) + completed = [b for b in batches if b['state'].lower() in ('success', 'failure')] + if not completed: + print('No completed ci/deploy/prod batches found', file=sys.stderr) + sys.exit(1) + return completed[0]['id'] + + +def find_sha(batch_id): + batch = json.loads(run(['hailctl', 'batch', 'get', str(batch_id), '-o', 'json']))[0] + return batch['attributes']['sha'] + + +def find_image(batch_id): + jobs = json.loads(run(['hailctl', 'batch', 'jobs', str(batch_id), '--name', 'batch_image', '-o', 'json'])) + if not jobs: + print(f'No batch_image job found in batch {batch_id}', file=sys.stderr) + sys.exit(1) + job_id = jobs[0]['job_id'] + job = json.loads(run(['hailctl', 'batch', 'job', str(batch_id), str(job_id), '-o', 'json']))[0] + command_parts = job['spec']['process']['command'] + command = next((p for p in command_parts if '--output' in p), None) + if command is None: + print('No --output flag found in batch_image job command', file=sys.stderr) + sys.exit(1) + match = re.search(r"--output 'type=image,\"name=([^,\"]+)", command) + if not match: + print('Could not find image name in batch_image job command', file=sys.stderr) + sys.exit(1) + return match.group(1) + + +print('Searching latest completed deploy batch for commit sha and image to scan...') +print() +batch_id = print_placeholder_during_lookup(' Batch', find_batch_id) +sha = print_placeholder_during_lookup(' SHA ', lambda: find_sha(batch_id)) +image = print_placeholder_during_lookup(' Image', lambda: find_image(batch_id)) + +BRANCH = 'main' +gh_cmd = ( + f'gh workflow run trivy-scan.yml --repo hail-is/hail --ref {BRANCH}' + f' -f branch={BRANCH} -f commit_hash={sha} -f images={image}' +) +gh_cmd_display = ( + f'gh workflow run trivy-scan.yml \\\n' + f' --repo hail-is/hail --ref {BRANCH} \\\n' + f' -f branch={BRANCH} \\\n' + f' -f commit_hash={sha} \\\n' + f' -f images={image}' +) + +print() +print(f'Proposed command:\n{gh_cmd_display}') +print() +try: + answer = input('Run it now? [y/N] ').strip().lower() +except EOFError: + answer = '' +if answer != 'y': + print('Aborted.') + sys.exit(0) + +subprocess.run( + [ + 'gh', 'workflow', 'run', 'trivy-scan.yml', + '--repo', 'hail-is/hail', + '--ref', BRANCH, + '-f', f'branch={BRANCH}', + '-f', f'commit_hash={sha}', + '-f', f'images={image}', + ], + check=True, +) +print('Workflow triggered.') From 29647b134766d6970099683d085e22377c666f5f Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 29 May 2026 13:17:09 -0400 Subject: [PATCH 2/4] filter completed batches directly --- devbin/trivy-scan-trigger.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/devbin/trivy-scan-trigger.py b/devbin/trivy-scan-trigger.py index 3e2e92c2207..4b4b37fa9df 100755 --- a/devbin/trivy-scan-trigger.py +++ b/devbin/trivy-scan-trigger.py @@ -43,12 +43,11 @@ def spin(): def find_batch_id(): - batches = json.loads(run(['hailctl', 'batch', 'list', '--query', 'batch_type=ci/deploy/prod', '--limit', '10', '-o', 'json'])) - completed = [b for b in batches if b['state'].lower() in ('success', 'failure')] - if not completed: + batches = json.loads(run(['hailctl', 'batch', 'list', '--query', 'batch_type=ci/deploy/prod complete', '--limit', '1', '-o', 'json'])) + if not batches: print('No completed ci/deploy/prod batches found', file=sys.stderr) sys.exit(1) - return completed[0]['id'] + return batches[0]['id'] def find_sha(batch_id): From f3dde2204741def589432dc11d77e7b816de8322 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 29 May 2026 13:20:08 -0400 Subject: [PATCH 3/4] main method --- devbin/trivy-scan-trigger.py | 89 +++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/devbin/trivy-scan-trigger.py b/devbin/trivy-scan-trigger.py index 4b4b37fa9df..ace5b823aac 100755 --- a/devbin/trivy-scan-trigger.py +++ b/devbin/trivy-scan-trigger.py @@ -74,45 +74,50 @@ def find_image(batch_id): return match.group(1) -print('Searching latest completed deploy batch for commit sha and image to scan...') -print() -batch_id = print_placeholder_during_lookup(' Batch', find_batch_id) -sha = print_placeholder_during_lookup(' SHA ', lambda: find_sha(batch_id)) -image = print_placeholder_during_lookup(' Image', lambda: find_image(batch_id)) - -BRANCH = 'main' -gh_cmd = ( - f'gh workflow run trivy-scan.yml --repo hail-is/hail --ref {BRANCH}' - f' -f branch={BRANCH} -f commit_hash={sha} -f images={image}' -) -gh_cmd_display = ( - f'gh workflow run trivy-scan.yml \\\n' - f' --repo hail-is/hail --ref {BRANCH} \\\n' - f' -f branch={BRANCH} \\\n' - f' -f commit_hash={sha} \\\n' - f' -f images={image}' -) - -print() -print(f'Proposed command:\n{gh_cmd_display}') -print() -try: - answer = input('Run it now? [y/N] ').strip().lower() -except EOFError: - answer = '' -if answer != 'y': - print('Aborted.') - sys.exit(0) - -subprocess.run( - [ - 'gh', 'workflow', 'run', 'trivy-scan.yml', - '--repo', 'hail-is/hail', - '--ref', BRANCH, - '-f', f'branch={BRANCH}', - '-f', f'commit_hash={sha}', - '-f', f'images={image}', - ], - check=True, -) -print('Workflow triggered.') +def main(): + print('Searching latest completed deploy batch for commit sha and image to scan...') + print() + batch_id = print_placeholder_during_lookup(' Batch', find_batch_id) + sha = print_placeholder_during_lookup(' SHA ', lambda: find_sha(batch_id)) + image = print_placeholder_during_lookup(' Image', lambda: find_image(batch_id)) + + BRANCH = 'main' + gh_cmd = ( + f'gh workflow run trivy-scan.yml --repo hail-is/hail --ref {BRANCH}' + f' -f branch={BRANCH} -f commit_hash={sha} -f images={image}' + ) + gh_cmd_display = ( + f'gh workflow run trivy-scan.yml \\\n' + f' --repo hail-is/hail --ref {BRANCH} \\\n' + f' -f branch={BRANCH} \\\n' + f' -f commit_hash={sha} \\\n' + f' -f images={image}' + ) + + print() + print(f'Proposed command:\n{gh_cmd_display}') + print() + try: + answer = input('Run it now? [y/N] ').strip().lower() + except EOFError: + answer = '' + if answer != 'y': + print('Aborted.') + sys.exit(0) + + subprocess.run( + [ + 'gh', 'workflow', 'run', 'trivy-scan.yml', + '--repo', 'hail-is/hail', + '--ref', BRANCH, + '-f', f'branch={BRANCH}', + '-f', f'commit_hash={sha}', + '-f', f'images={image}', + ], + check=True, + ) + print('Workflow triggered.') + + +if __name__ == '__main__': + main() From 4ca7529724c6ca8a9b271ca6cda2d058a64506f0 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 29 May 2026 16:00:06 -0400 Subject: [PATCH 4/4] handle no results case --- devbin/trivy-scan-trigger.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/devbin/trivy-scan-trigger.py b/devbin/trivy-scan-trigger.py index ace5b823aac..780c2b1eed7 100755 --- a/devbin/trivy-scan-trigger.py +++ b/devbin/trivy-scan-trigger.py @@ -43,7 +43,11 @@ def spin(): def find_batch_id(): - batches = json.loads(run(['hailctl', 'batch', 'list', '--query', 'batch_type=ci/deploy/prod complete', '--limit', '1', '-o', 'json'])) + out = run(['hailctl', 'batch', 'list', '--query', 'batch_type=ci/deploy/prod complete', '--limit', '1', '-o', 'json']) + if not out.strip() or not out.strip().startswith('['): + print('No completed ci/deploy/prod batches found', file=sys.stderr) + sys.exit(1) + batches = json.loads(out) if not batches: print('No completed ci/deploy/prod batches found', file=sys.stderr) sys.exit(1) @@ -56,7 +60,11 @@ def find_sha(batch_id): def find_image(batch_id): - jobs = json.loads(run(['hailctl', 'batch', 'jobs', str(batch_id), '--name', 'batch_image', '-o', 'json'])) + out = run(['hailctl', 'batch', 'jobs', str(batch_id), '--name', 'batch_image', '-o', 'json']) + if not out.strip() or not out.strip().startswith('['): + print(f'No batch_image job found in batch {batch_id}', file=sys.stderr) + sys.exit(1) + jobs = json.loads(out) if not jobs: print(f'No batch_image job found in batch {batch_id}', file=sys.stderr) sys.exit(1)