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