1#!/usr/bin/env python3
2
3# Sends an error report (if the report-error class was enabled) to a
4# remote server.
5#
6# Copyright (C) 2013 Intel Corporation
7# Author: Andreea Proca <andreea.b.proca@intel.com>
8# Author: Michael Wood <michael.g.wood@intel.com>
9#
10# SPDX-License-Identifier: GPL-2.0-only
11#
12
13import urllib.request, urllib.error
14import sys
15import json
16import os
17import subprocess
18import argparse
19import logging
20
21scripts_lib_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lib')
22sys.path.insert(0, scripts_lib_path)
23import argparse_oe
24
25version = "0.3"
26
27log = logging.getLogger("send-error-report")
28logging.basicConfig(format='%(levelname)s: %(message)s')
29
30def getPayloadLimit(url):
31    req = urllib.request.Request(url, None)
32    try:
33        response = urllib.request.urlopen(req)
34    except urllib.error.URLError as e:
35        # Use this opportunity to bail out if we can't even contact the server
36        log.error("Could not contact server: " + url)
37        log.error(e.reason)
38        sys.exit(1)
39    try:
40        ret = json.loads(response.read())
41        max_log_size = ret.get('max_log_size', 0)
42        return int(max_log_size)
43    except:
44        pass
45
46    return 0
47
48def ask_for_contactdetails():
49    print("Please enter your name and your email (optionally), they'll be saved in the file you send.")
50    username = input("Name (required): ")
51    email = input("E-mail (not required): ")
52    return username, email
53
54def edit_content(json_file_path):
55    edit = input("Review information before sending? (y/n): ")
56    if 'y' in edit or 'Y' in edit:
57        editor = os.environ.get('EDITOR', None)
58        if editor:
59            subprocess.check_call([editor, json_file_path])
60        else:
61            log.error("Please set your EDITOR value")
62            sys.exit(1)
63        return True
64    return False
65
66def prepare_data(args):
67    # attempt to get the max_log_size from the server's settings
68    max_log_size = getPayloadLimit(args.protocol+args.server+"/ClientPost/JSON")
69
70    if not os.path.isfile(args.error_file):
71        log.error("No data file found.")
72        sys.exit(1)
73
74    home = os.path.expanduser("~")
75    userfile = os.path.join(home, ".oe-send-error")
76
77    try:
78        with open(userfile, 'r') as userfile_fp:
79            if len(args.name) == 0:
80                args.name = userfile_fp.readline()
81            else:
82                #use empty readline to increment the fp
83                userfile_fp.readline()
84
85            if len(args.email) == 0:
86                args.email = userfile_fp.readline()
87    except:
88        pass
89
90    if args.assume_yes == True and len(args.name) == 0:
91        log.error("Name needs to be provided either via "+userfile+" or as an argument (-n).")
92        sys.exit(1)
93
94    while len(args.name) <= 0 or len(args.name) > 50:
95        print("\nName needs to be given and must not more than 50 characters.")
96        args.name, args.email = ask_for_contactdetails()
97
98    with open(userfile, 'w') as userfile_fp:
99        userfile_fp.write(args.name.strip() + "\n")
100        userfile_fp.write(args.email.strip() + "\n")
101
102    with open(args.error_file, 'r') as json_fp:
103        data = json_fp.read()
104
105        jsondata = json.loads(data)
106        jsondata['username'] = args.name.strip()
107        jsondata['email'] = args.email.strip()
108        jsondata['link_back'] = args.link_back.strip()
109        # If we got a max_log_size then use this to truncate to get the last
110        # max_log_size bytes from the end
111        if max_log_size != 0:
112            for fail in jsondata['failures']:
113                if len(fail['log']) > max_log_size:
114                    print("Truncating log to allow for upload")
115                    fail['log'] = fail['log'][-max_log_size:]
116
117        data = json.dumps(jsondata, indent=4, sort_keys=True)
118
119    # Write back the result which will contain all fields filled in and
120    # any post processing done on the log data
121    with open(args.error_file, "w") as json_fp:
122        if data:
123            json_fp.write(data)
124
125
126    if args.assume_yes == False and edit_content(args.error_file):
127        #We'll need to re-read the content if we edited it
128        with open(args.error_file, 'r') as json_fp:
129            data = json_fp.read()
130
131    return data.encode('utf-8')
132
133
134def send_data(data, args):
135    headers={'Content-type': 'application/json', 'User-Agent': "send-error-report/"+version}
136
137    if args.json:
138        url = args.protocol+args.server+"/ClientPost/JSON/"
139    else:
140        url = args.protocol+args.server+"/ClientPost/"
141
142    req = urllib.request.Request(url, data=data, headers=headers)
143    try:
144        response = urllib.request.urlopen(req)
145    except urllib.error.HTTPError as e:
146        logging.error(str(e))
147        sys.exit(1)
148
149    print(response.read().decode('utf-8'))
150
151
152if __name__ == '__main__':
153    arg_parse = argparse_oe.ArgumentParser(description="This scripts will send an error report to your specified error-report-web server.")
154
155    arg_parse.add_argument("error_file",
156                           help="Generated error report file location",
157                           type=str)
158
159    arg_parse.add_argument("-y",
160                           "--assume-yes",
161                           help="Assume yes to all queries and do not prompt",
162                           action="store_true")
163
164    arg_parse.add_argument("-s",
165                           "--server",
166                           help="Server to send error report to",
167                           type=str,
168                           default="errors.yoctoproject.org")
169
170    arg_parse.add_argument("-e",
171                           "--email",
172                           help="Email address to be used for contact",
173                           type=str,
174                           default="")
175
176    arg_parse.add_argument("-n",
177                           "--name",
178                           help="Submitter name used to identify your error report",
179                           type=str,
180                           default="")
181
182    arg_parse.add_argument("-l",
183                           "--link-back",
184                           help="A url to link back to this build from the error report server",
185                           type=str,
186                           default="")
187
188    arg_parse.add_argument("-j",
189                           "--json",
190                           help="Return the result in json format, silences all other output",
191                           action="store_true")
192
193    arg_parse.add_argument("--no-ssl",
194                           help="Use http instead of https protocol",
195                           dest="protocol",
196                           action="store_const", const="http://", default="https://")
197
198
199
200    args = arg_parse.parse_args()
201
202    if (args.json == False):
203        print("Preparing to send errors to: "+args.server)
204
205    data = prepare_data(args)
206    send_data(data, args)
207
208    sys.exit(0)
209