TUN-8631: Abort release on version mismatch

Closes TUN-8631

Approved-by: Gonçalo Garcia <ggarcia@cloudflare.com>
Approved-by: Devin Carr <dcarr@cloudflare.com>

MR: https://gitlab.cfdata.org/cloudflare/tun/cloudflared/-/merge_requests/1579
This commit is contained in:
Luis Neto 2024-10-11 02:44:29 -07:00
parent b426c62423
commit bade488bdf
1 changed files with 61 additions and 1 deletions

View File

@ -11,8 +11,9 @@ import hashlib
import requests import requests
import tarfile import tarfile
from os import listdir from os import listdir
from os.path import isfile, join from os.path import isfile, join, splitext
import re import re
import subprocess
from github import Github, GithubException, UnknownObjectException from github import Github, GithubException, UnknownObjectException
@ -210,6 +211,61 @@ def move_asset(filepath, filename):
except shutil.SameFileError: except shutil.SameFileError:
pass # the macOS release copy fails with being the same file (already in the artifacts directory) pass # the macOS release copy fails with being the same file (already in the artifacts directory)
def get_binary_version(binary_path):
"""
Sample output from go version -m <binary>:
...
build -compiler=gc
build -ldflags="-X \"main.Version=2024.8.3-6-gec072691\" -X \"main.BuildTime=2024-09-10-1027 UTC\" "
build CGO_ENABLED=1
...
This function parses the above output to retrieve the following substring 2024.8.3-6-gec072691.
To do this a start and end indexes are computed and the a slice is extracted from the output using them.
"""
needle = "main.Version="
cmd = ['go','version', '-m', binary_path]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, _ = process.communicate()
version_info = output.decode()
# Find start of needle
needle_index = version_info.find(needle)
# Find backward slash relative to the beggining of the needle
relative_end_index = version_info[needle_index:].find("\\")
# Calculate needle position plus needle length to find version beggining
start_index = needle_index + len(needle)
# Calculate needle position plus relative position of the backward slash
end_index = needle_index + relative_end_index
return version_info[start_index:end_index]
def assert_asset_version(binary_path, release_version):
"""
Asserts that the artifacts have the correct release_version.
The artifacts that are checked must not have an extension expecting .exe and .tgz.
In the occurrence of any other extension the function exits early.
"""
try:
shutil.rmtree('tmp')
except OSError:
pass
_, ext = os.path.splitext(binary_path)
if ext == '.exe' or ext == '':
binary_version = get_binary_version(binary_path)
elif ext == '.tgz':
tar = tarfile.open(binary_path, "r:gz")
tar.extractall("tmp", filter='data')
tar.close()
binary_path = os.path.join(os.getcwd(), 'tmp', 'cloudflared')
binary_version = get_binary_version(binary_path)
else:
return
if binary_version != release_version:
logging.error(f"Version mismatch {binary_path}, binary_version {binary_version} release_version {release_version}")
exit(1)
def main(): def main():
""" Attempts to upload Asset to Github Release. Creates Release if it doesn't exist """ """ Attempts to upload Asset to Github Release. Creates Release if it doesn't exist """
try: try:
@ -221,6 +277,7 @@ def main():
for filename in onlyfiles: for filename in onlyfiles:
binary_path = os.path.join(args.path, filename) binary_path = os.path.join(args.path, filename)
logging.info("binary: " + binary_path) logging.info("binary: " + binary_path)
assert_asset_version(binary_path, args.release_version)
elif os.path.isfile(args.path): elif os.path.isfile(args.path):
logging.info("binary: " + binary_path) logging.info("binary: " + binary_path)
else: else:
@ -233,6 +290,9 @@ def main():
if os.path.isdir(args.path): if os.path.isdir(args.path):
onlyfiles = [f for f in listdir(args.path) if isfile(join(args.path, f))] onlyfiles = [f for f in listdir(args.path) if isfile(join(args.path, f))]
for filename in onlyfiles:
binary_path = os.path.join(args.path, filename)
assert_asset_version(binary_path, args.release_version)
for filename in onlyfiles: for filename in onlyfiles:
binary_path = os.path.join(args.path, filename) binary_path = os.path.join(args.path, filename)
upload_asset(release, binary_path, filename, args.release_version, args.kv_account_id, args.namespace_id, upload_asset(release, binary_path, filename, args.release_version, args.kv_account_id, args.namespace_id,