1#!/usr/bin/env python 2# 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at http://mozilla.org/MPL/2.0/. 6# 7# This script uploads a symbol zip file from a path or URL passed on the commandline 8# to the symbol server at https://symbols.mozilla.org/ . 9# 10# Using this script requires you to have generated an authentication 11# token in the symbol server web interface. You must store the token in a Taskcluster 12# secret as the JSON blob `{"token": "<token>"}` and set the `SYMBOL_SECRET` 13# environment variable to the name of the Taskcluster secret. Alternately, 14# you can put the token in a file and set `SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE` 15# environment variable to the path to the file. 16 17from __future__ import absolute_import, print_function, unicode_literals 18 19import argparse 20import logging 21import os 22import sys 23from mozbuild.base import MozbuildObject 24log = logging.getLogger('upload-symbols') 25log.setLevel(logging.INFO) 26 27DEFAULT_URL = 'https://symbols.mozilla.org/upload/' 28MAX_RETRIES = 5 29 30 31def print_error(r): 32 if r.status_code < 400: 33 log.error('Error: bad auth token? ({0}: {1})'.format(r.status_code, 34 r.reason)) 35 else: 36 log.error('Error: got HTTP response {0}: {1}'.format(r.status_code, 37 r.reason)) 38 39 log.error('Response body:\n{sep}\n{body}\n{sep}\n'.format( 40 sep='=' * 20, 41 body=r.text 42 )) 43 44 45def get_taskcluster_secret(secret_name): 46 import requests 47 48 secrets_url = 'http://taskcluster/secrets/v1/secret/{}'.format(secret_name) 49 log.info( 50 'Using symbol upload token from the secrets service: "{}"'.format(secrets_url)) 51 res = requests.get(secrets_url) 52 res.raise_for_status() 53 secret = res.json() 54 auth_token = secret['secret']['token'] 55 56 return auth_token 57 58 59def main(): 60 config = MozbuildObject.from_environment() 61 config._activate_virtualenv() 62 63 import redo 64 import requests 65 66 logging.basicConfig() 67 parser = argparse.ArgumentParser( 68 description='Upload symbols in ZIP using token from Taskcluster secrets service.') 69 parser.add_argument('zip', 70 help='Symbols zip file - URL or path to local file') 71 args = parser.parse_args() 72 73 if not args.zip.startswith('http') and not os.path.isfile(args.zip): 74 log.error('Error: zip file "{0}" does not exist!'.format(args.zip)) 75 return 1 76 77 secret_name = os.environ.get('SYMBOL_SECRET') 78 if secret_name is not None: 79 auth_token = get_taskcluster_secret(secret_name) 80 elif 'SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE' in os.environ: 81 token_file = os.environ['SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE'] 82 83 if not os.path.isfile(token_file): 84 log.error('SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE "{0}" does not exist!'.format(token_file)) 85 return 1 86 auth_token = open(token_file, 'r').read().strip() 87 else: 88 log.error('You must set the SYMBOL_SECRET or SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE ' 89 'environment variables!') 90 return 1 91 92 # Allow overwriting of the upload url with an environmental variable 93 if 'SOCORRO_SYMBOL_UPLOAD_URL' in os.environ: 94 url = os.environ['SOCORRO_SYMBOL_UPLOAD_URL'] 95 else: 96 url = DEFAULT_URL 97 98 log.info('Uploading symbol file "{0}" to "{1}"'.format(args.zip, url)) 99 100 for i, _ in enumerate(redo.retrier(attempts=MAX_RETRIES), start=1): 101 log.info('Attempt %d of %d...' % (i, MAX_RETRIES)) 102 try: 103 if args.zip.startswith('http'): 104 zip_arg = {'data': {'url': args.zip}} 105 else: 106 zip_arg = {'files': {'symbols.zip': open(args.zip, 'rb')}} 107 r = requests.post( 108 url, 109 headers={'Auth-Token': auth_token}, 110 allow_redirects=False, 111 # Allow a longer read timeout because uploading by URL means the server 112 # has to fetch the entire zip file, which can take a while. The load balancer 113 # in front of symbols.mozilla.org has a 300 second timeout, so we'll use that. 114 timeout=(10, 300), 115 **zip_arg) 116 # 500 is likely to be a transient failure. 117 # Break out for success or other error codes. 118 if r.status_code < 500: 119 break 120 print_error(r) 121 except requests.exceptions.RequestException as e: 122 log.error('Error: {0}'.format(e)) 123 log.info('Retrying...') 124 else: 125 log.warn('Maximum retries hit, giving up!') 126 return 1 127 128 if r.status_code >= 200 and r.status_code < 300: 129 log.info('Uploaded successfully!') 130 return 0 131 132 print_error(r) 133 return 1 134 135 136if __name__ == '__main__': 137 sys.exit(main()) 138