summaryrefslogtreecommitdiffstats
path: root/scripts/send-error-report
blob: cfbcaa52cbc36712183065f46b2d10feb83dd0b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#!/usr/bin/env python3

# Sends an error report (if the report-error class was enabled) to a
# remote server.
#
# Copyright (C) 2013 Intel Corporation
# Author: Andreea Proca <andreea.b.proca@intel.com>
# Author: Michael Wood <michael.g.wood@intel.com>
#
# SPDX-License-Identifier: GPL-2.0-only
#

import urllib.request, urllib.error
import sys
import json
import os
import subprocess
import argparse
import logging

scripts_lib_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lib')
sys.path.insert(0, scripts_lib_path)
import argparse_oe

version = "0.3"

log = logging.getLogger("send-error-report")
logging.basicConfig(format='%(levelname)s: %(message)s')

def getPayloadLimit(url):
    req = urllib.request.Request(url, None)
    try:
        response = urllib.request.urlopen(req)
    except urllib.error.URLError as e:
        # Use this opportunity to bail out if we can't even contact the server
        log.error("Could not contact server: " + url)
        log.error(e.reason)
        sys.exit(1)
    try:
        ret = json.loads(response.read())
        max_log_size = ret.get('max_log_size', 0)
        return int(max_log_size)
    except:
        pass

    return 0

def ask_for_contactdetails():
    print("Please enter your name and your email (optionally), they'll be saved in the file you send.")
    username = input("Name (required): ")
    email = input("E-mail (not required): ")
    return username, email

def edit_content(json_file_path):
    edit = input("Review information before sending? (y/n): ")
    if 'y' in edit or 'Y' in edit:
        editor = os.environ.get('EDITOR', None)
        if editor:
            subprocess.check_call([editor, json_file_path])
        else:
            log.error("Please set your EDITOR value")
            sys.exit(1)
        return True
    return False

def prepare_data(args):
    # attempt to get the max_log_size from the server's settings
    max_log_size = getPayloadLimit(args.protocol+args.server+"/ClientPost/JSON")

    if not os.path.isfile(args.error_file):
        log.error("No data file found.")
        sys.exit(1)

    home = os.path.expanduser("~")
    userfile = os.path.join(home, ".oe-send-error")

    try:
        with open(userfile, 'r') as userfile_fp:
            if len(args.name) == 0:
                args.name = userfile_fp.readline()
            else:
                #use empty readline to increment the fp
                userfile_fp.readline()

            if len(args.email) == 0:
                args.email = userfile_fp.readline()
    except:
        pass

    if args.assume_yes == True and len(args.name) == 0:
        log.error("Name needs to be provided either via "+userfile+" or as an argument (-n).")
        sys.exit(1)

    while len(args.name) <= 0 or len(args.name) > 50:
        print("\nName needs to be given and must not more than 50 characters.")
        args.name, args.email = ask_for_contactdetails()

    with open(userfile, 'w') as userfile_fp:
        userfile_fp.write(args.name.strip() + "\n")
        userfile_fp.write(args.email.strip() + "\n")

    with open(args.error_file, 'r') as json_fp:
        data = json_fp.read()

        jsondata = json.loads(data)
        jsondata['username'] = args.name.strip()
        jsondata['email'] = args.email.strip()
        jsondata['link_back'] = args.link_back.strip()
        # If we got a max_log_size then use this to truncate to get the last
        # max_log_size bytes from the end
        if max_log_size != 0:
            for fail in jsondata['failures']:
                if len(fail['log']) > max_log_size:
                    print("Truncating log to allow for upload")
                    fail['log'] = fail['log'][-max_log_size:]

        data = json.dumps(jsondata, indent=4, sort_keys=True)

    # Write back the result which will contain all fields filled in and
    # any post processing done on the log data
    with open(args.error_file, "w") as json_fp:
        if data:
            json_fp.write(data)


    if args.assume_yes == False and edit_content(args.error_file):
        #We'll need to re-read the content if we edited it
        with open(args.error_file, 'r') as json_fp:
            data = json_fp.read()

    return data.encode('utf-8')


def send_data(data, args):
    headers={'Content-type': 'application/json', 'User-Agent': "send-error-report/"+version}

    if args.json:
        url = args.protocol+args.server+"/ClientPost/JSON/"
    else:
        url = args.protocol+args.server+"/ClientPost/"

    req = urllib.request.Request(url, data=data, headers=headers)
    try:
        response = urllib.request.urlopen(req)
    except urllib.error.HTTPError as e:
        logging.error(str(e))
        sys.exit(1)

    print(response.read().decode('utf-8'))


if __name__ == '__main__':
    arg_parse = argparse_oe.ArgumentParser(description="This scripts will send an error report to your specified error-report-web server.")

    arg_parse.add_argument("error_file",
                           help="Generated error report file location",
                           type=str)

    arg_parse.add_argument("-y",
                           "--assume-yes",
                           help="Assume yes to all queries and do not prompt",
                           action="store_true")

    arg_parse.add_argument("-s",
                           "--server",
                           help="Server to send error report to",
                           type=str,
                           default="errors.yoctoproject.org")

    arg_parse.add_argument("-e",
                           "--email",
                           help="Email address to be used for contact",
                           type=str,
                           default="")

    arg_parse.add_argument("-n",
                           "--name",
                           help="Submitter name used to identify your error report",
                           type=str,
                           default="")

    arg_parse.add_argument("-l",
                           "--link-back",
                           help="A url to link back to this build from the error report server",
                           type=str,
                           default="")

    arg_parse.add_argument("-j",
                           "--json",
                           help="Return the result in json format, silences all other output",
                           action="store_true")

    arg_parse.add_argument("--no-ssl",
                           help="Use http instead of https protocol",
                           dest="protocol",
                           action="store_const", const="http://", default="https://")



    args = arg_parse.parse_args()

    if (args.json == False):
        print("Preparing to send errors to: "+args.server)

    data = prepare_data(args)
    send_data(data, args)

    sys.exit(0)
>"<event>") or not event.endswith("</event>"): print("Error, not an event %s" % event) return event = pickle.loads(event[7:-8]) fire_ui_handlers(event, d) noop = lambda _: None def register(name, handler): """Register an Event handler""" # already registered if name in _handlers: return AlreadyRegistered if handler is not None: # handle string containing python code if isinstance(handler, basestring): tmp = "def %s(e):\n%s" % (name, handler) try: code = compile(tmp, "%s(e)" % name, "exec") except SyntaxError: logger.error("Unable to register event handler '%s':\n%s", name, ''.join(traceback.format_exc(limit=0))) _handlers[name] = noop return env = {} bb.utils.better_exec(code, env) func = bb.utils.better_eval(name, env) _handlers[name] = func else: _handlers[name] = handler return Registered def remove(name, handler): """Remove an Event handler""" _handlers.pop(name) def register_UIHhandler(handler): bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1 _ui_handlers[_ui_handler_seq] = handler return _ui_handler_seq def unregister_UIHhandler(handlerNum): if handlerNum in _ui_handlers: del _ui_handlers[handlerNum] return def getName(e): """Returns the name of a class or class instance""" if getattr(e, "__name__", None) == None: return e.__class__.__name__ else: return e.__name__ class OperationStarted(Event): """An operation has begun""" def __init__(self, msg = "Operation Started"): Event.__init__(self) self.msg = msg class OperationCompleted(Event): """An operation has completed""" def __init__(self, total, msg = "Operation Completed"): Event.__init__(self) self.total = total self.msg = msg class OperationProgress(Event): """An operation is in progress""" def __init__(self, current, total, msg = "Operation in Progress"): Event.__init__(self) self.current = current self.total = total self.msg = msg + ": %s/%s" % (current, total); class ConfigParsed(Event): """Configuration Parsing Complete""" class RecipeEvent(Event): def __init__(self, fn): self.fn = fn Event.__init__(self) class RecipePreFinalise(RecipeEvent): """ Recipe Parsing Complete but not yet finialised""" class RecipeParsed(RecipeEvent): """ Recipe Parsing Complete """ class StampUpdate(Event): """Trigger for any adjustment of the stamp files to happen""" def __init__(self, targets, stampfns): self._targets = targets self._stampfns = stampfns Event.__init__(self) def getStampPrefix(self): return self._stampfns def getTargets(self): return self._targets stampPrefix = property(getStampPrefix) targets = property(getTargets) class BuildBase(Event): """Base class for bbmake run events""" def __init__(self, n, p, failures = 0): self._name = n self._pkgs = p Event.__init__(self) self._failures = failures def getPkgs(self): return self._pkgs def setPkgs(self, pkgs): self._pkgs = pkgs def getName(self): return self._name def setName(self, name): self._name = name def getCfg(self): return self.data def setCfg(self, cfg): self.data = cfg def getFailures(self): """ Return the number of failed packages """ return self._failures pkgs = property(getPkgs, setPkgs, None, "pkgs property") name = property(getName, setName, None, "name property") cfg = property(getCfg, setCfg, None, "cfg property") class BuildStarted(BuildBase, OperationStarted): """bbmake build run started""" def __init__(self, n, p, failures = 0): OperationStarted.__init__(self, "Building Started") BuildBase.__init__(self, n, p, failures) class BuildCompleted(BuildBase, OperationCompleted): """bbmake build run completed""" def __init__(self, total, n, p, failures = 0): if not failures: OperationCompleted.__init__(self, total, "Building Succeeded") else: OperationCompleted.__init__(self, total, "Building Failed") BuildBase.__init__(self, n, p, failures) class DiskFull(Event): """Disk full case build aborted""" def __init__(self, dev, type, freespace, mountpoint): Event.__init__(self) self._dev = dev self._type = type self._free = freespace self._mountpoint = mountpoint class NoProvider(Event): """No Provider for an Event""" def __init__(self, item, runtime=False, dependees=None, reasons=[]): Event.__init__(self) self._item = item self._runtime = runtime self._dependees = dependees self._reasons = reasons def getItem(self): return self._item def isRuntime(self): return self._runtime class MultipleProviders(Event): """Multiple Providers""" def __init__(self, item, candidates, runtime = False): Event.__init__(self) self._item = item self._candidates = candidates self._is_runtime = runtime def isRuntime(self): """ Is this a runtime issue? """ return self._is_runtime def getItem(self): """ The name for the to be build item """ return self._item def getCandidates(self): """ Get the possible Candidates for a PROVIDER. """ return self._candidates class ParseStarted(OperationStarted): """Recipe parsing for the runqueue has begun""" def __init__(self, total): OperationStarted.__init__(self, "Recipe parsing Started") self.total = total class ParseCompleted(OperationCompleted): """Recipe parsing for the runqueue has completed""" def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total): OperationCompleted.__init__(self, total, "Recipe parsing Completed") self.cached = cached self.parsed = parsed self.skipped = skipped self.virtuals = virtuals self.masked = masked self.errors = errors self.sofar = cached + parsed class ParseProgress(OperationProgress): """Recipe parsing progress""" def __init__(self, current, total): OperationProgress.__init__(self, current, total, "Recipe parsing") class CacheLoadStarted(OperationStarted): """Loading of the dependency cache has begun""" def __init__(self, total): OperationStarted.__init__(self, "Loading cache Started") self.total = total class CacheLoadProgress(OperationProgress): """Cache loading progress""" def __init__(self, current, total): OperationProgress.__init__(self, current, total, "Loading cache") class CacheLoadCompleted(OperationCompleted): """Cache loading is complete""" def __init__(self, total, num_entries): OperationCompleted.__init__(self, total, "Loading cache Completed") self.num_entries = num_entries class TreeDataPreparationStarted(OperationStarted): """Tree data preparation started""" def __init__(self): OperationStarted.__init__(self, "Preparing tree data Started") class TreeDataPreparationProgress(OperationProgress): """Tree data preparation is in progress""" def __init__(self, current, total): OperationProgress.__init__(self, current, total, "Preparing tree data") class TreeDataPreparationCompleted(OperationCompleted): """Tree data preparation completed""" def __init__(self, total): OperationCompleted.__init__(self, total, "Preparing tree data Completed") class DepTreeGenerated(Event): """ Event when a dependency tree has been generated """ def __init__(self, depgraph): Event.__init__(self) self._depgraph = depgraph class TargetsTreeGenerated(Event): """ Event when a set of buildable targets has been generated """ def __init__(self, model): Event.__init__(self) self._model = model class FilesMatchingFound(Event): """ Event when a list of files matching the supplied pattern has been generated """ def __init__(self, pattern, matches): Event.__init__(self) self._pattern = pattern self._matches = matches class CoreBaseFilesFound(Event): """ Event when a list of appropriate config files has been generated """ def __init__(self, paths): Event.__init__(self) self._paths = paths class ConfigFilesFound(Event): """ Event when a list of appropriate config files has been generated """ def __init__(self, variable, values): Event.__init__(self) self._variable = variable self._values = values class ConfigFilePathFound(Event): """ Event when a path for a config file has been found """ def __init__(self, path): Event.__init__(self) self._path = path class MsgBase(Event): """Base class for messages""" def __init__(self, msg): self._message = msg Event.__init__(self) class MsgDebug(MsgBase): """Debug Message""" class MsgNote(MsgBase): """Note Message""" class MsgWarn(MsgBase): """Warning Message""" class MsgError(MsgBase): """Error Message""" class MsgFatal(MsgBase): """Fatal Message""" class MsgPlain(MsgBase): """General output""" class LogExecTTY(Event): """Send event containing program to spawn on tty of the logger""" def __init__(self, msg, prog, sleep_delay, retries): Event.__init__(self) self.msg = msg self.prog = prog self.sleep_delay = sleep_delay self.retries = retries class LogHandler(logging.Handler): """Dispatch logging messages as bitbake events""" def emit(self, record): if record.exc_info: etype, value, tb = record.exc_info if hasattr(tb, 'tb_next'): tb = list(bb.exceptions.extract_traceback(tb, context=3)) record.bb_exc_info = (etype, value, tb) record.exc_info = None fire(record, None) def filter(self, record): record.taskpid = worker_pid return True class RequestPackageInfo(Event): """ Event to request package information """ class PackageInfo(Event): """ Package information for GUI """ def __init__(self, pkginfolist): Event.__init__(self) self._pkginfolist = pkginfolist class SanityCheck(Event): """ Event to issue sanity check """ class SanityCheckPassed(Event): """ Event to indicate sanity check is passed """ class SanityCheckFailed(Event): """ Event to indicate sanity check has failed """ def __init__(self, msg, network_error=False): Event.__init__(self) self._msg = msg self._network_error = network_error class NetworkTest(Event): """ Event to start network test """ class NetworkTestPassed(Event): """ Event to indicate network test has passed """ class NetworkTestFailed(Event): """ Event to indicate network test has failed """