Files
brave-core/script/omaha-upload.py
T

356 lines
15 KiB
Python

#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import argparse
import logging
import os
import re
import sys
import requests
from argparse import RawTextHelpFormatter
from lib.config import get_brave_version, get_raw_version, get_env_var
from lib.connect import post, get, post_with_file
from lib.github import GitHub
from lib.helpers import *
from lib.util import get_host_arch, omaha_channel
from lib.omaha import get_app_info, get_base64_authorization, get_channel_id, get_omaha_version_id, \
get_upload_version, get_event_id, get_channel_ids_from_omaha_server, sign_update_sparkle
# TODO:
# 1. write tests
# 2. create other apps
# API DOCUMENTATION
# created id jenkins-upload account(PW IN 1password) on the `updates-panel-dev` omaha server
# to generate base64 string on commandline use 'echo -n userid:password" | base64'
# THE jenkins-upload USER CAN ONLY ADD OMAHA OR SPARKLE VERSION, CANNOT DELETE OR MODIFY EXISTING VERSIONS
# EXAMPLE CURL
# curl -L -D- -X GET -H "Authorization: Basic BASE64USERIDANDPASSWORD" -H "Content-Type: application/json" \
# "https://OMAHA-HOSTNAME/api/omaha/version"
# https://crystalnix.github.io/omaha-server/#header-supplying-basic-auth-headers
# https://crystalnix.github.io/omaha-server/#omaha-version-version-list-post
# https://crystalnix.github.io/omaha-server/#sparkle-version-version-list-post
# Example of using API to communicate with Sparkle:
# https://gist.github.com/yurtaev/294a5fbd78016e5d7456
def download_from_github(args, logging):
file_list = []
# BRAVE_REPO defined in helpers.py
repo = GitHub(get_env_var('GITHUB_TOKEN')).repos(BRAVE_REPO)
if args.tag:
tag_name = args.tag
else:
tag_name = get_brave_version()
release = None
releases = get_releases_by_tag(repo, tag_name, include_drafts=True)
if releases:
if len(releases) > 1:
exit("Error: More than 1 release exists with the tag: \'{}\'".format(tag_name))
release = releases[0]
else:
exit("Error: Did not get the release \'{}\' from Github.".format(tag_name))
found_assets_in_github_release = {}
for asset in release['assets']:
if re.match(r'.*\.dmg$', asset['name']):
if args.uploaded:
if not args.platform:
args.platform = []
args.platform.append('darwin')
found_assets_in_github_release['darwin'] = {}
found_assets_in_github_release['darwin']['name'] = asset['name']
found_assets_in_github_release['darwin']['url'] = asset['url']
elif re.match(r'brave_installer-ia32\.exe$', asset['name']):
if args.uploaded:
if not args.platform:
args.platform = []
args.platform.append('win32')
found_assets_in_github_release['win32'] = {}
found_assets_in_github_release['win32']['name'] = asset['name']
found_assets_in_github_release['win32']['url'] = asset['url']
elif re.match(r'brave_installer-x64\.exe$', asset['name']):
if args.uploaded:
if not args.platform:
args.platform = []
args.platform.append('win64')
found_assets_in_github_release['win64'] = {}
found_assets_in_github_release['win64']['name'] = asset['name']
found_assets_in_github_release['win64']['url'] = asset['url']
logging.debug("Found assets in github release: {}".format(
found_assets_in_github_release))
for requested_platform in args.platform:
logging.debug("Verifying platform \'{}\' exists in GitHub release".
format(requested_platform))
if requested_platform not in found_assets_in_github_release.keys():
logging.error("Platform \'{}\' does not exist in GitHub release".
format(requested_platform))
exit(1)
for platform in args.platform:
if args.debug:
logging.debug("GitHub asset_url: {}".format(
found_assets_in_github_release[platform]['url'] + '/'
+ found_assets_in_github_release[platform]['name']))
# Instantiate new requests session, versus reusing the repo session above.
# Headers was likely being reused in that session, and not allowing us
# to set the Accept header to the below.
headers = {'Accept': 'application/octet-stream',
'Authorization': 'token ' + get_env_var('GITHUB_TOKEN')}
asset_url = found_assets_in_github_release[platform]['url']
if args.debug:
# disable urllib3 logging for this session to avoid showing
# access_token in logs
logging.getLogger("urllib3").setLevel(logging.WARNING)
r = requests.get(asset_url, headers=headers, stream=True)
if args.debug:
logging.getLogger("urllib3").setLevel(logging.DEBUG)
logging.debug("Writing GitHub download to file: {}".format(
found_assets_in_github_release[platform]['name']))
with open(found_assets_in_github_release[platform]['name'], 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
logging.debug(
"Requests Response status_code: {}".format(r.status_code))
if r.status_code == 200:
file_list.append('./' + found_assets_in_github_release[platform]['name'])
else:
logging.debug(
"Requests Response status_code != 200: {}".format(r.status_code))
if len(file_list) < len(args.platform):
for item in args.platform:
logging.error(
"Cannot get requested file from Github! {}".format(found_assets_in_github_release[item]['name']))
remove_github_downloaded_files(file_list, logging)
exit(1)
return file_list
def remove_github_downloaded_files(file_list, logging):
for source_file in file_list:
try:
os.remove(source_file)
logging.debug("Removed file: {}".format(source_file))
except OSError:
raise
def post_action(host, version, action, headers, args):
url = 'https://' + host + '/api/action/'
params = {
"version": version,
"event": get_event_id(action)
}
if release_channel() in ['nightly']:
params['arguments'] = "--chrome-sxs"
elif release_channel() not in ['release']:
params['arguments'] = "--chrome-" + release_channel()
if args.debug:
logging.debug("params: ")
for item in params:
logging.debug('{}: {}'.format(item, params[item]))
response = post(url, params, headers)
if response.status_code != 201:
logging.error("ERROR: Action not created! response.status_code : {}".format(
response.status_code))
exit(1)
def parse_args():
desc = "Upload Windows/Mac install files to Omaha server" \
"\n\nRequires the following ENVIRONMENT VARIABLES be set:" \
"\n\nCHANNEL: The Brave channel, i.e. \'nightly\', \'dev\', \'beta\', \'release\'" \
"\nOMAHA_HOST: The FQDN hostname of the Omaha server to upload to. (without \'https:\\\\' prefix)" \
"\nOMAHA_USER: The UserID to use to login to the Omaha server." \
"\nOMAHA_PASS: The Password to login to the Omaha server." \
"\nDSA_PRIVATE_PEM: The Private DSA pem file used to sign the Mac DMG file." \
"\nBRAVE_GITHUB_TOKEN: Github token to download from a draft release if not published yet. " \
"(ONLY REQUIRED IF --github)" \
"\nnpm_config_brave_version: Chromium version (only if not in brave-core directory with brave-browser" \
" in the parent dir)"
parser = argparse.ArgumentParser(
description=desc, formatter_class=RawTextHelpFormatter)
parser.add_argument('--version', help='full brave version to upload')
parser.add_argument('--previous', help='previous version')
parser.add_argument('--platform', help='platforms (spaced)', nargs='*', choices=['win32', 'win64', 'darwin'])
parser.add_argument('--internal', help='upload to internal test channels', action='store_true')
parser.add_argument('--full', help='upload to "-full" channels', action='store_true')
parser.add_argument('--file', help='installer file to upload (cannot be combined with --github)')
parser.add_argument('--tag', help='GitHub version tag to upload')
parser.add_argument('--github', help='download from GitHub (cannot be combined with --file)', action='store_true')
parser.add_argument('--uploaded', help='upload all the platforms from the GitHub release', action='store_true')
parser.add_argument('--debug', help='debug', action='store_true')
return parser.parse_args()
def main():
args = parse_args()
if args.debug:
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logging.debug('brave_version: {}'.format(get_upload_version()))
if args.version and args.previous:
if args.version == args.previous:
exit("Error: version and previous have to be different")
if args.uploaded and args.platform:
exit("Error: --platform and --uploaded are mutually exclusive, only one allowed")
# Default to requiring all 3 platforms
if not args.uploaded and not args.platform:
args.platform = ['win32', 'win64', 'darwin']
if args.debug and args.platform:
logging.debug("args.platform: {}".format(args.platform))
if args.file and args.github:
exit("Error: --file and --github are mutually exclusive, only one allowed")
if not os.environ.get('OMAHA_PASS') or not os.environ.get('OMAHA_USER'):
message = ('Error: Please set the $OMAHA_USER, $OMAHA_PASS and $OMAHA_HOST environment variables')
exit(message)
if args.github:
file_list = download_from_github(args, logging)
else:
file_list = [args.file]
for source_file in file_list:
app_info = {}
if args.debug:
logging.debug("source_file: {}".format(source_file))
if args.github:
if re.match(r'.*\.dmg$', source_file):
app_info['platform'] = 'darwin'
app_info['arch'] = 'x64'
elif re.match(r'.*brave_installer-ia32\.exe$', source_file):
app_info['platform'] = 'win32'
app_info['arch'] = 'ia32'
elif re.match(r'.*brave_installer-x64\.exe$', source_file):
app_info['platform'] = 'win32'
app_info['arch'] = 'x64'
app_info = get_app_info(app_info, args)
app_info['omahahost'] = os.environ.get('OMAHA_HOST')
app_info['auth'] = get_base64_authorization(os.environ.get('OMAHA_USER'), os.environ.get('OMAHA_PASS'))
app_info['headers'] = headers = {
'Authorization': 'Basic %s' % app_info['auth']
}
if app_info['previous']:
app_info['version_url'] = '/api/deltaupdate/'
else:
if app_info['platform'] in 'darwin':
app_info['version_url'] = '/api/sparkle/version/'
if not os.environ.get('DSA_PRIVATE_PEM'):
exit('Error: Please set the $DSA_PRIVATE_PEM environment variable')
elif app_info['platform'] in 'win32':
app_info['version_url'] = '/api/omaha/version/'
app_info['version_post_url'] = 'https://' + app_info['omahahost'] + app_info['version_url']
app_info['size'] = os.path.getsize(source_file)
channel = omaha_channel(app_info['platform'], app_info['arch'], app_info['internal'], app_info['full'])
channel_id = get_channel_id(channel, app_info['omahahost'], app_info['headers'], logging)
if args.debug:
for item in app_info:
if item in 'auth':
logging.debug('{}: {}'.format(item, "NOTAREALPASSWORD"))
elif item in 'headers':
logging.debug('{}: {}'.format(
item, "{'Authorization': 'Basic NOTAREALPASSWORD'}"))
else:
logging.debug('{}: {}'.format(item, app_info[item]))
logging.debug("omaha_channel: {}".format(channel))
logging.debug("omaha_channel_id: {}".format(channel_id))
logging.debug("URL: {}".format(app_info['version_post_url']))
logging.debug("file_list: {}".format(file_list))
with open(source_file, 'rb') as f:
files = {'file': f}
if not app_info['previous']:
params = {
'app': app_info['appguid'],
'channel': channel_id,
'version': app_info['version'],
'release_notes': app_info['release_notes']
}
if app_info['platform'] in 'win32':
params['is_enabled'] = app_info['is_enabled']
params['platform'] = app_info['platform_id']
else:
app_info['darwindsasig'] = sign_update_sparkle(
source_file, os.environ.get('DSA_PRIVATE_PEM')).rstrip('\n')
params['dsa_signature'] = app_info['darwindsasig']
params['short_version'] = app_info['short_version']
elif 'win64' in args.platform:
target_version_id = get_omaha_version_id(channel, args.version, app_info['omahahost'],
app_info['headers'], logging)
source_version_id = get_omaha_version_id(channel, args.previous, app_info['omahahost'],
app_info['headers'], logging)
params = {
'target': target_version_id,
'source': source_version_id
}
response = post_with_file(app_info['version_post_url'], files, params, headers)
if response.status_code != 201:
logging.error("ERROR: Version not created! response.status_code : {}".format(response.status_code))
logging.error("response.text : {}".format(response.text))
if response.status_code == 400 and 'version must make a unique set' in response.text:
logging.error("ERROR: This version({}), channel({}), appguid({}) set has already been "
"uploaded!".format(params['version'], app_info['channel'], params['app']))
exit(1)
if not app_info['previous'] and (app_info['platform'] in 'win32'):
# When uploading windows builds, add actions to version just created
rjson = response.json()
if args.debug:
logging.debug("response['id']: {}".format(rjson['id']))
post_action(app_info['omahahost'], rjson['id'], 'install', headers, args)
post_action(app_info['omahahost'], rjson['id'], 'update', headers, args)
# if downloading from github, remove files after upload
if args.github:
remove_github_downloaded_files(file_list, logging)
if __name__ == '__main__':
sys.exit(main())