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