Skip to content

Commit 093b4d7

Browse files
committed
feat: Add the ability to match the version of a release
As we move to containerized deploys we don't want to have to build a new image to incorporate he updated hash from the release branch. This adds the ability to expose a JSON blog with a "version" key that Doof can monitor to determine if a release was deployed.
1 parent 21ca04a commit 093b4d7

File tree

2 files changed

+76
-15
lines changed

2 files changed

+76
-15
lines changed

bot.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ async def _web_application_release(
456456
)
457457

458458
async def _wait_for_deploy_with_alerts(
459-
self, *, repo_info, release_pr, hash_url, watch_branch
459+
self, *, repo_info, release_pr, hash_url, watch_branch, expected_version
460460
):
461461
"""
462462
Wait for a deployment, but alert with a a timeout
@@ -471,6 +471,7 @@ async def _wait_for_deploy_with_alerts(
471471
repo_url=repo_url,
472472
hash_url=hash_url,
473473
watch_branch=watch_branch,
474+
expected_version=expected_version,
474475
timeout_seconds=timeout_seconds,
475476
):
476477
break
@@ -490,12 +491,15 @@ async def _wait_for_deploy_rc(self, *, repo_info, manager, release_pr):
490491
"""
491492
repo_url = repo_info.repo_url
492493
channel_id = repo_info.channel_id
494+
# Get the expected version from the release PR title
495+
expected_version = release_pr.version
493496

494497
await self._wait_for_deploy_with_alerts(
495498
repo_info=repo_info,
496499
release_pr=release_pr,
497500
hash_url=repo_info.rc_hash_url,
498501
watch_branch="release-candidate",
502+
expected_version=expected_version,
499503
)
500504

501505
rc_server = remove_path_from_url(repo_info.rc_hash_url)
@@ -525,17 +529,15 @@ async def _wait_for_deploy_prod(self, *, repo_info, manager, release_pr):
525529
"""
526530
repo_url = repo_info.repo_url
527531
channel_id = repo_info.channel_id
528-
version = await get_version_tag(
529-
github_access_token=self.github_access_token,
530-
repo_url=repo_url,
531-
commit_hash="origin/release",
532-
)
532+
# Get the expected version from the release PR title (which should match the tag)
533+
expected_version = release_pr.version
533534

534535
await self._wait_for_deploy_with_alerts(
535536
repo_info=repo_info,
536537
release_pr=release_pr,
537538
hash_url=repo_info.prod_hash_url,
538539
watch_branch="release",
540+
expected_version=expected_version,
539541
)
540542

541543
await set_release_label(
@@ -550,7 +552,7 @@ async def _wait_for_deploy_prod(self, *, repo_info, manager, release_pr):
550552
await self.say(
551553
channel_id=channel_id,
552554
text=(
553-
f"My evil scheme {version} for {repo_info.name} has been released to production at {prod_server}. "
555+
f"My evil scheme {expected_version} for {repo_info.name} has been released to production at {prod_server}. "
554556
"And by 'released', I mean completely...um...leased."
555557
),
556558
)

wait_for_deploy.py

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,67 @@
11
"""Wait for hash on server to match with deployed code"""
22
import asyncio
3+
import json
4+
import logging
35
import time
46

57
from async_subprocess import check_output
68
from client_wrapper import ClientWrapper
7-
from release import init_working_dir
9+
from lib import init_working_dir # Import from lib.py
10+
from version import get_version_tag
811

12+
log = logging.getLogger(__name__)
913

10-
async def fetch_release_hash(hash_url):
11-
"""Fetch the hash from the release"""
14+
15+
async def fetch_release_hash(hash_url, *, expected_version=None):
16+
"""
17+
Fetch the hash from the release URL.
18+
19+
Handles both plain text hash responses and JSON responses containing a 'hash' key.
20+
"""
1221
client = ClientWrapper()
1322
response = await client.get(hash_url)
1423
response.raise_for_status()
15-
release_hash = response.content.decode().strip()
24+
content = response.content.decode().strip()
25+
release_hash = None
26+
27+
try:
28+
data = json.loads(content)
29+
if isinstance(data, dict):
30+
# If we expect a specific version, check it first
31+
if expected_version and "version" in data:
32+
deployed_version = str(data["version"]).strip()
33+
if deployed_version != expected_version:
34+
raise Exception(
35+
f"Version mismatch at {hash_url}: Expected '{expected_version}', but found '{deployed_version}'"
36+
)
37+
# If version matches (or wasn't checked), get the hash
38+
if "hash" in data:
39+
release_hash = str(data["hash"]).strip()
40+
except json.JSONDecodeError:
41+
# Content is not JSON, treat it as a plain hash
42+
pass
43+
44+
if release_hash is None:
45+
# Fallback: Treat the entire content as the hash if JSON parsing failed
46+
# or the 'hash' key wasn't found.
47+
release_hash = content
48+
1649
if len(release_hash) != 40:
50+
# Validate the final hash string
1751
raise Exception(
18-
f"Expected release hash from {hash_url} but got: {release_hash}"
52+
f"Expected a 40-character release hash from {hash_url} but got: '{release_hash}'"
1953
)
2054
return release_hash
2155

2256

2357
async def wait_for_deploy(
24-
*, github_access_token, repo_url, hash_url, watch_branch, timeout_seconds=60 * 60
58+
*,
59+
github_access_token,
60+
repo_url,
61+
hash_url,
62+
watch_branch,
63+
expected_version,
64+
timeout_seconds=60 * 60,
2565
):
2666
"""
2767
Wait until server is finished with the deploy
@@ -31,6 +71,7 @@ async def wait_for_deploy(
3171
repo_url (str): The repository URL which has the latest commit hash to check
3272
hash_url (str): The deployment URL which has the commit of the deployed app
3373
watch_branch (str): The branch in the repository which has the latest commit
74+
expected_version (str or None): The version string expected to be found at the hash_url, or None to skip version check.
3475
timeout_seconds (int): The number of seconds to wait before timing out the deploy
3576
3677
Returns:
@@ -44,9 +85,27 @@ async def wait_for_deploy(
4485
["git", "rev-parse", f"origin/{watch_branch}"], cwd=working_dir
4586
)
4687
latest_hash = output.decode().strip()
47-
while await fetch_release_hash(hash_url) != latest_hash:
88+
89+
if expected_version:
90+
log.info(f"Expecting version '{expected_version}' for hash {latest_hash[:7]}")
91+
else:
92+
log.info(f"No specific version expected for hash {latest_hash[:7]}. Proceeding without version check.")
93+
94+
while True:
95+
try:
96+
current_hash = await fetch_release_hash(hash_url, expected_version=expected_version)
97+
if current_hash == latest_hash:
98+
log.info(f"Hash {latest_hash[:7]} confirmed at {hash_url}")
99+
break # Hashes match, deploy successful
100+
else:
101+
log.info(f"Waiting for hash {latest_hash[:7]} at {hash_url}, currently {current_hash[:7]}")
102+
except Exception as e: # pylint: disable=broad-except
103+
log.error(f"Error checking deploy status at {hash_url}: {e}")
104+
# Optionally, decide if specific errors should stop the wait
105+
48106
if (time.time() - start_time) > timeout_seconds:
49-
return False
107+
log.error(f"Timeout waiting for hash {latest_hash[:7]} at {hash_url}")
108+
return False # Timeout reached
50109
await asyncio.sleep(30)
51110

52111
return True

0 commit comments

Comments
 (0)