123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- #!/usr/bin/python3
- """
- Creates Github Releases and uploads assets
- """
- import argparse
- import logging
- import os
- import shutil
- import hashlib
- import requests
- import tarfile
- from github import Github, GithubException, UnknownObjectException
- FORMAT = "%(levelname)s - %(asctime)s: %(message)s"
- logging.basicConfig(format=FORMAT)
- CLOUDFLARED_REPO = os.environ.get("GITHUB_REPO", "cloudflare/cloudflared")
- GITHUB_CONFLICT_CODE = "already_exists"
- BASE_KV_URL = 'https://api.cloudflare.com/client/v4/accounts/'
- UPDATER_PREFIX = 'update'
- def get_sha256(filename):
- """ get the sha256 of a file """
- sha256_hash = hashlib.sha256()
- with open(filename,"rb") as f:
- for byte_block in iter(lambda: f.read(4096),b""):
- sha256_hash.update(byte_block)
- return sha256_hash.hexdigest()
- def send_hash(pkg_hash, name, version, account, namespace, api_token):
- """ send the checksum of a file to workers kv """
- key = '{0}_{1}_{2}'.format(UPDATER_PREFIX, version, name)
- headers = {
- "Content-Type": "application/json",
- "Authorization": "Bearer " + api_token,
- }
- response = requests.put(
- BASE_KV_URL + account + "/storage/kv/namespaces/" + namespace + "/values/" + key,
- headers=headers,
- data=pkg_hash
- )
- if response.status_code != 200:
- jsonResponse = response.json()
- errors = jsonResponse["errors"]
- if len(errors) > 0:
- raise Exception("failed to upload checksum: {0}", errors[0])
- def assert_tag_exists(repo, version):
- """ Raise exception if repo does not contain a tag matching version """
- tags = repo.get_tags()
- if not tags or tags[0].name != version:
- raise Exception("Tag {} not found".format(version))
- def get_or_create_release(repo, version, dry_run=False):
- """
- Get a Github Release matching the version tag or create a new one.
- If a conflict occurs on creation, attempt to fetch the Release on last time
- """
- try:
- release = repo.get_release(version)
- logging.info("Release %s found", version)
- return release
- except UnknownObjectException:
- logging.info("Release %s not found", version)
- # We dont want to create a new release tag if one doesnt already exist
- assert_tag_exists(repo, version)
- if dry_run:
- logging.info("Skipping Release creation because of dry-run")
- return
- try:
- logging.info("Creating release %s", version)
- return repo.create_git_release(version, version, "")
- except GithubException as e:
- errors = e.data.get("errors", [])
- if e.status == 422 and any(
- [err.get("code") == GITHUB_CONFLICT_CODE for err in errors]
- ):
- logging.warning(
- "Conflict: Release was likely just made by a different build: %s",
- e.data,
- )
- return repo.get_release(version)
- raise e
- def parse_args():
- """ Parse and validate args """
- parser = argparse.ArgumentParser(
- description="Creates Github Releases and uploads assets."
- )
- parser.add_argument(
- "--api-key", default=os.environ.get("API_KEY"), help="Github API key"
- )
- parser.add_argument(
- "--release-version",
- metavar="version",
- default=os.environ.get("VERSION"),
- help="Release version",
- )
- parser.add_argument(
- "--path", default=os.environ.get("ASSET_PATH"), help="Asset path"
- )
- parser.add_argument(
- "--name", default=os.environ.get("ASSET_NAME"), help="Asset Name"
- )
- parser.add_argument(
- "--namespace-id", default=os.environ.get("KV_NAMESPACE"), help="workersKV namespace id"
- )
- parser.add_argument(
- "--kv-account-id", default=os.environ.get("KV_ACCOUNT"), help="workersKV account id"
- )
- parser.add_argument(
- "--kv-api-token", default=os.environ.get("KV_API_TOKEN"), help="workersKV API Token"
- )
- parser.add_argument(
- "--dry-run", action="store_true", help="Do not create release or upload asset"
- )
- args = parser.parse_args()
- is_valid = True
- if not args.release_version:
- logging.error("Missing release version")
- is_valid = False
- if not args.path:
- logging.error("Missing asset path")
- is_valid = False
- if not args.name:
- logging.error("Missing asset name")
- is_valid = False
- if not args.api_key:
- logging.error("Missing API key")
- is_valid = False
-
- if not args.namespace_id:
- logging.error("Missing KV namespace id")
- is_valid = False
- if not args.kv_account_id:
- logging.error("Missing KV account id")
- is_valid = False
- if not args.kv_api_token:
- logging.error("Missing KV API token")
- is_valid = False
- if is_valid:
- return args
- parser.print_usage()
- exit(1)
- def main():
- """ Attempts to upload Asset to Github Release. Creates Release if it doesnt exist """
- try:
- args = parse_args()
- client = Github(args.api_key)
- repo = client.get_repo(CLOUDFLARED_REPO)
- release = get_or_create_release(repo, args.release_version, args.dry_run)
- if args.dry_run:
- logging.info("Skipping asset upload because of dry-run")
- return
- release.upload_asset(args.path, name=args.name)
- # check and extract if the file is a tar and gzipped file (as is the case with the macos builds)
- binary_path = args.path
- if binary_path.endswith("tgz"):
- try:
- shutil.rmtree('cfd')
- except OSError as e:
- pass
- zipfile = tarfile.open(binary_path, "r:gz")
- zipfile.extractall('cfd') # specify which folder to extract to
- zipfile.close()
- binary_path = os.path.join(os.getcwd(), 'cfd', 'cloudflared')
- # send the sha256 (the checksum) to workers kv
- pkg_hash = get_sha256(binary_path)
- send_hash(pkg_hash, args.name, args.release_version, args.kv_account_id, args.namespace_id, args.kv_api_token)
- # create the artifacts directory if it doesn't exist
- artifact_path = os.path.join(os.getcwd(), 'artifacts')
- if not os.path.isdir(artifact_path):
- os.mkdir(artifact_path)
- # copy the binary to the path
- copy_path = os.path.join(artifact_path, args.name)
- try:
- shutil.copy(args.path, copy_path)
- except shutil.SameFileError:
- pass # the macOS release copy fails with being the same file (already in the artifacts directory). Catching to ignore.
- except Exception as e:
- logging.exception(e)
- exit(1)
- main()
|