Skip to content

Commit 9eb98b3

Browse files
committed
rbio deploy: add deploy:branch, deploy:repo, deploy:verify-images
1 parent d73dfc3 commit 9eb98b3

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

bin/lib/deploy.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
"""deploy:* — deploy-time templating + ops invoked from infrastructure/."""
22

33
import argparse
4+
import json
45
import os
56
import re
7+
import subprocess
8+
import urllib.error
9+
import urllib.request
610
from pathlib import Path
711

12+
from lib._docker import ALL_IMAGES
813
from lib._runtime import REPO_ROOT, Globals, stderr
914

15+
# Branch -> Dockerhub repo. Replaces scripts/common.sh::get_deploy_repo.
16+
DEPLOY_REPOS = {
17+
"master": "ccdl",
18+
"dev": "ccdlstaging",
19+
}
20+
1021
# Per-template RAM fan-out for the workers project. A template not listed here
1122
# uses DEFAULT_WORKER_RAMS; one in NO_RAM_WORKER_TEMPLATES is rendered once
1223
# without a RAM postfix.
@@ -197,6 +208,147 @@ def _render(text, env):
197208
return TEMPLATE_VAR_RE.sub(lambda m: env.get(m.group(1), m.group(0)), text)
198209

199210

211+
def cmd_deploy_branch(argv):
212+
p = argparse.ArgumentParser(
213+
prog="rbio deploy:branch",
214+
description=(
215+
"Print the deploy branch (master/dev/unknown) a version tag was cut from. "
216+
"Mirrors scripts/common.sh::get_deploy_branch."
217+
),
218+
formatter_class=argparse.RawDescriptionHelpFormatter,
219+
)
220+
p.add_argument("version", help="version tag (e.g. v1.2.3 or v1.2.3-dev)")
221+
args = p.parse_args(argv)
222+
223+
on_master = _tag_reachable_from(args.version, "origin/master")
224+
on_dev = _tag_reachable_from(args.version, "origin/dev")
225+
# All staging versions end -dev or -dev-hotfix; production never does.
226+
has_dev_suffix = bool(re.search(r"-dev(-hotfix)?$", args.version))
227+
228+
if on_master and not has_dev_suffix:
229+
print("master")
230+
elif on_dev:
231+
print("dev")
232+
else:
233+
print("unknown")
234+
return 0
235+
236+
237+
def _tag_reachable_from(version, remote_branch):
238+
"""True if `version` appears anywhere in `git log <remote_branch> --decorate=full`.
239+
240+
Mirrors common.sh's `git log origin/X --decorate=full | grep "$version"` heuristic.
241+
"""
242+
result = subprocess.run(
243+
["git", "log", remote_branch, "--decorate=full"],
244+
capture_output=True,
245+
text=True,
246+
cwd=str(REPO_ROOT),
247+
)
248+
return result.returncode == 0 and version in result.stdout
249+
250+
251+
def cmd_deploy_repo(argv):
252+
p = argparse.ArgumentParser(
253+
prog="rbio deploy:repo",
254+
description=(
255+
"Print the Dockerhub repo namespace for a deploy branch (master -> ccdl, "
256+
"dev -> ccdlstaging). Mirrors scripts/common.sh::get_deploy_repo."
257+
),
258+
formatter_class=argparse.RawDescriptionHelpFormatter,
259+
)
260+
p.add_argument("branch", choices=sorted(DEPLOY_REPOS))
261+
args = p.parse_args(argv)
262+
print(DEPLOY_REPOS[args.branch])
263+
return 0
264+
265+
266+
def cmd_deploy_verify_images(argv):
267+
p = argparse.ArgumentParser(
268+
prog="rbio deploy:verify-images",
269+
description=(
270+
"Verify every image in ALL_IMAGES has been pushed to Dockerhub at the "
271+
"given tag. Used post-deploy to catch silent push failures."
272+
),
273+
epilog=(
274+
"env vars:\n"
275+
" DOCKER_USERNAME Dockerhub login (required)\n"
276+
" DOCKER_PASSWORD Dockerhub password / access token (required)\n"
277+
),
278+
formatter_class=argparse.RawDescriptionHelpFormatter,
279+
)
280+
p.add_argument(
281+
"-r", "--repo", required=True, help="Dockerhub repo namespace (e.g. ccdlstaging)"
282+
)
283+
p.add_argument("-t", "--tag", required=True, help="image tag to verify (e.g. v1.2.3)")
284+
args = p.parse_args(argv)
285+
286+
user = os.environ.get("DOCKER_USERNAME")
287+
password = os.environ.get("DOCKER_PASSWORD")
288+
if not user or not password:
289+
stderr("rbio deploy:verify-images: DOCKER_USERNAME and DOCKER_PASSWORD must be set")
290+
return 1
291+
292+
token = _dockerhub_token(user, password)
293+
if not token:
294+
stderr("rbio deploy:verify-images: failed to authenticate with Dockerhub")
295+
return 1
296+
297+
missing = []
298+
for image in ALL_IMAGES:
299+
repo_path = f"{args.repo}/dr_{image}"
300+
if not _dockerhub_tag_exists(token, repo_path, args.tag):
301+
missing.append(f"{repo_path}:{args.tag}")
302+
303+
if missing:
304+
stderr(f"rbio deploy:verify-images: {len(missing)} image(s) not found on Dockerhub:")
305+
for m in missing:
306+
stderr(f" {m}")
307+
stderr("This is generally caused by a temporary push error; rerun the workflow.")
308+
return 1
309+
return 0
310+
311+
312+
def _dockerhub_token(username, password):
313+
"""Authenticate to Dockerhub v2 API and return a JWT, or None on failure."""
314+
req = urllib.request.Request(
315+
"https://hub.docker.com/v2/users/login/",
316+
data=json.dumps({"username": username, "password": password}).encode(),
317+
headers={"Content-Type": "application/json"},
318+
method="POST",
319+
)
320+
try:
321+
with urllib.request.urlopen(req) as resp:
322+
return json.loads(resp.read()).get("token")
323+
except (urllib.error.URLError, json.JSONDecodeError):
324+
return None
325+
326+
327+
def _dockerhub_tag_exists(token, repo, tag):
328+
"""True if `repo:tag` is listed in Dockerhub's tag index for `repo`."""
329+
req = urllib.request.Request(
330+
f"https://hub.docker.com/v2/repositories/{repo}/tags/?page_size=10000",
331+
headers={"Authorization": f"JWT {token}"},
332+
)
333+
try:
334+
with urllib.request.urlopen(req) as resp:
335+
data = json.loads(resp.read())
336+
return any(t.get("name") == tag for t in data.get("results", []))
337+
except (urllib.error.URLError, json.JSONDecodeError):
338+
return False
339+
340+
200341
COMMANDS = [
201342
("deploy:format-batch", cmd_deploy_format_batch, "render Batch job specs/env from templates"),
343+
(
344+
"deploy:branch",
345+
cmd_deploy_branch,
346+
"print the deploy branch (master/dev/unknown) for a version tag",
347+
),
348+
("deploy:repo", cmd_deploy_repo, "print the Dockerhub repo namespace for a deploy branch"),
349+
(
350+
"deploy:verify-images",
351+
cmd_deploy_verify_images,
352+
"verify all ALL_IMAGES were pushed at a given tag",
353+
),
202354
]

0 commit comments

Comments
 (0)