Files
letro-android/tools/github/download_github_artifacts.py
Jorge Martin Espinosa ad2263f464 Add use existing branch confirmation and progress for file download (#6294)
* Add `use existing branch for release` confirmation. Otherwise, this message might go unnoticed and we might build the wrong binaries

* Display the progress of downloaded artifacts so we can be sure the process is working
2026-03-06 14:45:47 +01:00

165 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (c) 2025 Element Creations Ltd.
# Copyright 2022-2025 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
# Please see LICENSE files in the repository root for full details.
#
import argparse
import hashlib
import json
import os
# Run `pip3 install requests` if not installed yet
import requests
# Run `pip3 install re` if not installed yet
import re
# This script downloads artifacts from GitHub.
# Ref: https://docs.github.com/en/rest/actions/artifacts#get-an-artifact
error = False
### Arguments
parser = argparse.ArgumentParser(description='Download artifacts from GitHub.')
parser.add_argument('-t',
'--token',
required=True,
help='The GitHub token with read access.')
parser.add_argument('-a',
'--artifactUrl',
required=True,
help='the artifact_url from GitHub.')
parser.add_argument('-f',
'--filename',
help='the filename, if not provided, will use the artifact name.')
parser.add_argument('-i',
'--ignoreErrors',
help='Ignore errors that can be ignored. Build state and number of artifacts.',
action="store_true")
parser.add_argument('-d',
'--directory',
default="",
help='the target directory, where files will be downloaded. If not provided the artifactId will be used to create a directory.')
parser.add_argument('-v',
'--verbose',
help="increase output verbosity.",
action="store_true")
parser.add_argument('-s',
'--simulate',
help="simulate action, do not create folder or download any file.",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("Argument:")
print(args)
# Split the artifact URL to get information
# Ex: https://github.com/element-hq/element-x-android/actions/runs/7299827320/artifacts/1131077517
artifactUrl = args.artifactUrl
# if artifactUrl starts with https://github.com
if artifactUrl.startswith('https://github.com'):
url_regex = r"https://github.com/(.+?)/(.+?)/actions/runs/.+?/artifacts/(.+)"
result = re.search(url_regex, artifactUrl)
if result is None:
print(
"❌ Invalid parameter --artifactUrl '%s'. Please check the format.\nIt should be something like: %s" %
(artifactUrl, 'https://github.com/element-hq/element-x-android/actions/runs/7299827320/artifacts/1131077517')
)
exit(1)
else:
url_regex = r"https://api.github.com/repos/(.+?)/(.+?)/actions/artifacts/(.+)"
result = re.search(url_regex, artifactUrl)
if result is None:
print(
"❌ Invalid parameter --artifactUrl '%s'. Please check the format.\nIt should be something like: %s" %
(artifactUrl, 'https://api.github.com/repos/element-hq/element-x-android/actions/artifacts/1131077517')
)
exit(1)
(gitHubRepoOwner, gitHubRepo, artifactId) = result.groups()
if args.verbose:
print("gitHubRepoOwner: %s, gitHubRepo: %s, artifactId: %s" % (gitHubRepoOwner, gitHubRepo, artifactId))
headers = {
'Authorization': "Bearer %s" % args.token,
'Accept': 'application/vnd.github+json'
}
base_url = "https://api.github.com/repos/%s/%s/actions/artifacts/%s" % (gitHubRepoOwner, gitHubRepo, artifactId)
### Fetch build state
print("Getting artifacts data of project '%s/%s' artifactId '%s'..." % (gitHubRepoOwner, gitHubRepo, artifactId))
if args.verbose:
print("Url: %s" % base_url)
r = requests.get(base_url, headers=headers)
data = json.loads(r.content.decode())
if args.verbose:
print("Json data:")
print(data)
if args.verbose:
print("Create subfolder %s to download artifacts..." % artifactId)
if args.directory == "":
targetDir = artifactId
else:
targetDir = args.directory
if not args.simulate:
os.makedirs(targetDir, exist_ok=True)
url = data.get("archive_download_url")
if args.filename is not None:
filename = args.filename
else:
filename = data.get("name") + ".zip"
## Print some info about the artifact origin
commitLink = "https://github.com/%s/%s/commit/%s" % (gitHubRepoOwner, gitHubRepo, data.get("workflow_run").get("head_sha"))
print("Preparing to download artifact `%s`, built from branch: `%s` (commit %s)" % (data.get("name"), data.get("workflow_run").get("head_branch"), commitLink))
if args.verbose:
print()
print("Artifact url: %s" % url)
target = targetDir + "/" + filename
sizeInBytes = data.get("size_in_bytes")
print("Downloading %s to '%s' (file size is %s bytes, this may take a while)..." % (filename, targetDir, sizeInBytes))
if not args.simulate:
# open file to write in binary mode
with open(target, "wb") as file:
# get request
with requests.get(url, headers=headers, stream=True) as response:
total = int(response.headers.get('Content-Length', 0))
totalStr = "{0:.2f}".format(total / 1024 / 1024)
for chunk in response.iter_content(chunk_size=65536):
if chunk: # filter out keep-alive new chunks
file.write(chunk)
current = "{0:.2f}".format(file.tell() / 1024 / 1024)
print(f"Downloaded {current}/{totalStr} MB", end="\r")
print("Verifying file size...")
# get the file size
size = os.path.getsize(target)
if sizeInBytes != size:
# error = True
print("Warning, file size mismatch: expecting %s and get %s. This is just a warning for now..." % (sizeInBytes, size))
if error:
print("❌ Error(s) occurred, please check the log")
exit(1)
else:
print("Done!")