send-error-report 7.5 KB

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