Skip to content

Commit ae47c6f

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 3e28fe3 commit ae47c6f

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,28 +1,68 @@
11
"""Wait for hash on server to match with deployed code"""
22

33
import asyncio
4+
import json
5+
import logging
46
import time
57

68
from async_subprocess import check_output
79
from client_wrapper import ClientWrapper
8-
from release import init_working_dir
10+
from lib import init_working_dir # Import from lib.py
11+
from version import get_version_tag
912

13+
log = logging.getLogger(__name__)
1014

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

2357

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

53112
return True

0 commit comments

Comments
 (0)