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