diff options
author | kiko%async.com.br <> | 2003-10-31 23:00:19 +0100 |
---|---|---|
committer | kiko%async.com.br <> | 2003-10-31 23:00:19 +0100 |
commit | 4d5799ba61bd0d5be1a7148c8b2fff6db1871a01 (patch) | |
tree | e8e20a55f278d04d4e2d6f74cf239d26722d7d92 /contrib/bugzilla-submit | |
parent | 5a0e0a7735ff396d0a58b8c2d20b16cb0bcf6979 (diff) | |
download | bugzilla-4d5799ba61bd0d5be1a7148c8b2fff6db1871a01.tar.gz bugzilla-4d5799ba61bd0d5be1a7148c8b2fff6db1871a01.tar.xz |
Fix for Bug 220724: Provide standalone bug submission program. Includes
a python script that submits bugs to a specified Bugzilla instance.
README, docs and an example bug are included. Work done by Eric Raymond
<esr@thyrsus.com> and myself. a=justdave
Diffstat (limited to 'contrib/bugzilla-submit')
-rw-r--r-- | contrib/bugzilla-submit/README | 46 | ||||
-rw-r--r-- | contrib/bugzilla-submit/bugdata.txt | 11 | ||||
-rwxr-xr-x | contrib/bugzilla-submit/bugzilla-submit | 279 | ||||
-rw-r--r-- | contrib/bugzilla-submit/bugzilla-submit.xml | 199 |
4 files changed, 535 insertions, 0 deletions
diff --git a/contrib/bugzilla-submit/README b/contrib/bugzilla-submit/README new file mode 100644 index 000000000..08bc3d85c --- /dev/null +++ b/contrib/bugzilla-submit/README @@ -0,0 +1,46 @@ +Bug-Bugzilla +============ + +Authors: Christian Reis <kiko@async.com.br> + Eric Raymond <esr@thyrsus.com> + +Bug-Bugzilla is a simple Python program that creates bugs in a Bugzilla +instance. It takes as input text resembling message headers (RFC-822 +formatted) via standard input, or optionally a number of commandline +parameters. It communicates using HTTP, which allows it to work over a +network. + +Requirements +------------ + +Its only requirement is Python 2.3 or higher; you should have the +"python" executable in your path. + +Usage Notes +----------- + +* Please constrain testing to your own installation of Bugzilla, or use +* http://landfill.bugzilla.org/ for testing purposes -- opening test +* bugs on production instances of Bugzilla is definitely not a good idea + +Run "bug-bugzilla --help" for a description of the possible options. + +An example input file, named bugdata.txt, is provided. You can pipe it +in as standard input to bug-bugzilla, providing a Bugzilla URI through +the command-line. + +Note that you must create a ~/.netrc entry to authenticate against the +Bugzilla instance. The entry's machine field is a *quoted* Bugzilla URI, +the login field is your ID on that host, and the password field is the +your password password. An example entry follows: + + machine "http://bugzilla.mozilla.org/" + login foo@bar.loo + password snarf + +Documentation +------------- + +Documentation for bug-bugzilla is provided in Docbook format; see +bug-bugzilla.xml. + diff --git a/contrib/bugzilla-submit/bugdata.txt b/contrib/bugzilla-submit/bugdata.txt new file mode 100644 index 000000000..56f70b9f1 --- /dev/null +++ b/contrib/bugzilla-submit/bugdata.txt @@ -0,0 +1,11 @@ +Product: FoodReplicator +Component: Salt +Version: 1.0 +Priority: P2 +Hardware: PC +OS: Linux +Severity: critical +Summary: Impending electron shortage + +We need an emergency supply of electrons. + diff --git a/contrib/bugzilla-submit/bugzilla-submit b/contrib/bugzilla-submit/bugzilla-submit new file mode 100755 index 000000000..e16a968e7 --- /dev/null +++ b/contrib/bugzilla-submit/bugzilla-submit @@ -0,0 +1,279 @@ +#!/usr/bin/env python +# +# bug-bugzilla: a command-line script to post bugs to a Bugzilla instance +# +# Authors: Christian Reis <kiko@async.com.br> +# Eric S. Raymond <esr@thyrsus.com> +# +# This is version 0.5. +# +# For a usage hint run bug-bugzilla --help +# +# TODO: use RDF output to pick up valid options, as in +# http://www.async.com.br/~kiko/mybugzilla/config.cgi?ctype=rdf + +import sys + +def error(m): + sys.stderr.write("bug-bugzilla: %s\n" % m) + sys.stderr.flush() + sys.exit(1) + +if sys.version[:6] < '2.3.0': + error("you must upgrade to Python 2.3 or higher to use this script.") + +import urllib, re, os, netrc, email.Parser, optparse + +# Set up some aliases -- partly to hide the less friendly fieldnames +# behind the names actually used for them in the stock web page presentation, +# and partly to provide a place for mappings if the Bugzilla fieldnames +# ever change. +field_aliases = (('hardware', 'rep_platform'), + ('os', 'op_sys'), + ('summary', 'short_desc'), + ('description', 'comment'), + ('depends-on', 'dependson'), + ('status', 'bug_status'), + ('severity', 'bug_severity'), + ('URL', 'bug_file_loc'),) + +def header_to_field(hdr): + hdr = hdr.lower().replace("-", "_") + for (alias, name) in field_aliases: + if hdr == alias: + hdr = name + break + return hdr + +def field_to_header(hdr): + hdr = "-".join(map(lambda x: x.capitalize(), hdr.split("_"))) + for (alias, name) in field_aliases: + if hdr == name: + hdr = alias + break + return hdr + +def setup_parser(): + # Take override values from the command line + parser = optparse.OptionParser(usage="usage: %prog [options] bugzilla-url") + parser.add_option('-b', '--status', dest='bug_status', + help='Set the Status field.') + parser.add_option('-u', '--url', dest='bug_file_loc', + help='Set the URL field.') + parser.add_option('-p', '--product', dest='product', + help='Set the Product field.') + parser.add_option('-v', '--version', dest='version', + help='Set the Version field.') + parser.add_option('-c', '--component', dest='component', + help='Set the Component field.') + parser.add_option('-s', '--summary', dest='short_desc', + help='Set the Summary field.') + parser.add_option('-H', '--hardware', dest='rep_platform', + help='Set the Hardware field.') + parser.add_option('-o', '--os', dest='op_sys', + help='Set the Operating-system field.') + parser.add_option('-r', '--priority', dest='priority', + help='Set the Priority field.') + parser.add_option('-x', '--severity', dest='bug_severity', + help='Set the Severity field.') + parser.add_option('-d', '--description', dest='comment', + help='Set the Description field.') + parser.add_option('-a', '--assigned-to', dest='assigned_to', + help='Set the Assigned-To field.') + parser.add_option('-C', '--cc', dest='cc', + help='Set the Cc field.') + parser.add_option('-k', '--keywords', dest='keywords', + help='Set the Keywords field.') + parser.add_option('-D', '--depends-on', dest='dependson', + help='Set the Depends-On field.') + parser.add_option('-B', '--blocked', dest='blocked', + help='Set the Blocked field.') + parser.add_option('-n', '--no-stdin', dest='read', + default=True, action='store_false', + help='Suppress reading fields from stdin.') + return parser + +# Fetch user's credential for access to this Bugzilla instance. +def get_credentials(bugzilla): + # Work around a quirk in the Python implementation. + # The URL has to be quoted, otherwise the parser barfs on the colon. + # But the parser doesn't strip the quotes. + authenticate_on = '"' + bugzilla + '"' + try: + credentials = netrc.netrc() + except netrc.NetrcParseError, e: + error("ill-formed .netrc: %s:%s %s" % (e.filename, e.lineno, e.msg)) + except IOError, e: + error("missing .netrc file %s" % str(e).split()[-1]) + ret = credentials.authenticators(authenticate_on) + if not ret: + # Apparently, an invalid machine URL will cause credentials == None + error("no credentials for Bugzilla instance at %s" % bugzilla) + return ret + +def process_options(options): + data = {} + # Initialize bug report fields from message on standard input + if options.read: + message_parser = email.Parser.Parser() + message = message_parser.parse(sys.stdin) + for (key, value) in message.items(): + data.update({header_to_field(key) : value}) + if not 'comment' in data: + data['comment'] = message.get_payload() + + # Merge in options from the command line; they override what's on stdin. + for (key, value) in options.__dict__.items(): + if key != 'read' and value != None: + data[key] = value + return data + +def ensure_defaults(data): + # Provide some defaults if the user did not supply them. + if 'op_sys' not in data: + if sys.platform.startswith('linux'): + data['op_sys'] = 'Linux' + if 'rep_platform' not in data: + data['rep_platform'] = 'PC' + if 'bug_status' not in data: + data['bug_status'] = 'NEW' + if 'bug_severity' not in data: + data['bug_severity'] = 'normal' + if 'bug_file_loc' not in data: + data['bug_file_loc'] = 'http://' # Yes, Bugzilla needs this + if 'priority' not in data: + data['priority'] = 'P2' + +def validate_fields(data): + # Fields for validation + required_fields = ( + "bug_status", "bug_file_loc", "product", "version", "component", + "short_desc", "rep_platform", "op_sys", "priority", "bug_severity", + "comment", + ) + legal_fields = required_fields + ( + "assigned_to", "cc", "keywords", "dependson", "blocked", + ) + my_fields = data.keys() + for field in my_fields: + if field not in legal_fields: + error("invalid field: %s" % field_to_header(field)) + for field in required_fields: + if field not in my_fields: + error("required field missing: %s" % field_to_header(field)) + + if not data['short_desc']: + error("summary for bug submission must not be empty") + + if not data['comment']: + error("comment for bug submission must not be empty") + +# +# POST-specific functions +# + +def submit_bug_POST(bugzilla, data): + # Move the request over the wire + postdata = urllib.urlencode(data) + url = urllib.urlopen("%s/post_bug.cgi" % bugzilla, postdata) + ret = url.read() + check_result_POST(ret, data) + +def check_result_POST(ret, data): + # XXX We can move pre-validation out of here as soon as we pick up + # the valid options from config.cgi -- it will become a simple + # assertion and ID-grabbing step. + # + # XXX: We use get() here which may return None, but since the user + # might not have provided these options, we don't want to die on + # them. + version = data.get('version') + product = data.get('product') + component = data.get('component') + priority = data.get('priority') + severity = data.get('bug_severity') + status = data.get('bug_status') + assignee = data.get('assigned_to') + platform = data.get('rep_platform') + opsys = data.get('op_sys') + keywords = data.get('keywords') + deps = data.get('dependson', '') + " " + data.get('blocked', '') + deps = deps.replace(" ", ", ") + # XXX: we should really not be using plain find() here, as it can + # match the bug content inadvertedly + if ret.find("A legal Version was not") != -1: + error("version %r does not exist for component %s:%s" % + (version, product, component)) + if ret.find("A legal Priority was not") != -1: + error("priority %r does not exist in " + "this Bugzilla instance" % priority) + if ret.find("A legal Severity was not") != -1: + error("severity %r does not exist in " + "this Bugzilla instance" % severity) + if ret.find("A legal Status was not") != -1: + error("status %r is not a valid creation status in " + "this Bugzilla instance" % status) + if ret.find("A legal Platform was not") != -1: + error("platform %r is not a valid platform in " + "this Bugzilla instance" % platform) + if ret.find("A legal OS/Version was not") != -1: + error("%r is not a valid OS in " + "this Bugzilla instance" % opsys) + if ret.find("Invalid Username") != -1: + error("invalid credentials submitted") + if ret.find("Component Needed") != -1: + error("the component %r does not exist in " + "this Bugzilla instance" % component) + if ret.find("Unknown Keyword") != -1: + error("keyword(s) %r not registered in " + "this Bugzilla instance" % keywords) + if ret.find("The product name") != -1: + error("Product %r does not exist in this " + "Bugzilla instance" % product) + # XXX: this should be smarter + if ret.find("does not exist") != -1: + error("Could not mark dependencies for bugs %s: one or " + "more bugs didn't exist in this Bugzilla instance" % deps) + if ret.find("Match Failed") != -1: + # XXX: invalid CC hits on this error too + error("the bug assignee %r isn't registered in " + "this Bugzilla instance" % assignee) + # If all is well, return bug number posted + m = re.search("Bug ([0-9]+) Submitted", ret) + if not m: + print ret + error("Internal error: bug id not found; please report a bug") + id = m.group(1) + print "Bug %s posted." % id + +# +# +# + +if __name__ == "__main__": + parser = setup_parser() + + # Parser will print help and exit here if we specified --help + (options, args) = parser.parse_args() + + if len(args) != 1: + parser.error("missing Bugzilla host URL") + + bugzilla = args[0] + data = process_options(options) + + login, account, password = get_credentials(bugzilla) + if "@" not in login: # no use even trying to submit + error("login %r is invalid (it should be an email address)" % login) + + ensure_defaults(data) + validate_fields(data) + + # Attach authentication information + data.update({'Bugzilla_login' : login, + 'Bugzilla_password' : password, + 'GoAheadAndLogIn' : 1, + 'form_name' : 'enter_bug'}) + + submit_bug_POST(bugzilla, data) + diff --git a/contrib/bugzilla-submit/bugzilla-submit.xml b/contrib/bugzilla-submit/bugzilla-submit.xml new file mode 100644 index 000000000..da8a34c77 --- /dev/null +++ b/contrib/bugzilla-submit/bugzilla-submit.xml @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!DOCTYPE refentry PUBLIC + "-//OASIS//DTD DocBook XML V4.1.2//EN" + "docbook/docbookx.dtd"> +<refentry id='bug-bugzilla.1'> +<refmeta> +<refentrytitle>bug-bugzilla</refentrytitle> +<manvolnum>1</manvolnum> +<refmiscinfo class='date'>Oct 30, 2003</refmiscinfo> +</refmeta> +<refnamediv id='name'> +<refname>bug-bugzilla</refname> +<refpurpose>post bugs to a Bugzilla instance</refpurpose> +</refnamediv> +<refsynopsisdiv id='synopsis'> + +<cmdsynopsis> + <command>bug-bugzilla</command> + <arg choice='opt'>--status <replaceable>bug_status</replaceable></arg> + <arg choice='opt'>--url <replaceable>bug_file_loc</replaceable></arg> + <arg choice='opt'>--product <replaceable>product</replaceable></arg> + <arg choice='opt'>--version <replaceable>version</replaceable></arg> + <arg choice='opt'>--component <replaceable>component</replaceable></arg> + <arg choice='opt'>--summary <replaceable>short_desc</replaceable></arg> + <arg choice='opt'>--hardware <replaceable>rep_platform</replaceable></arg> + <arg choice='opt'>--os <replaceable>op_sys</replaceable></arg> + <arg choice='opt'>--priority <replaceable>priority</replaceable></arg> + <arg choice='opt'>--severity <replaceable>bug_severity</replaceable></arg> + <arg choice='opt'>--assigned-to <replaceable>assigned-to</replaceable></arg> + <arg choice='opt'>--cc <replaceable>cc</replaceable></arg> + <arg choice='opt'>--keywords <replaceable>keywords</replaceable></arg> + <arg choice='opt'>--depends-on <replaceable>dependson</replaceable></arg> + <arg choice='opt'>--blocked <replaceable>blocked</replaceable></arg> + <arg choice='opt'>--description <replaceable>comment</replaceable></arg> + <arg choice='opt'>--no-read </arg> + <arg choice='plain'><replaceable>bugzilla-url</replaceable></arg> +</cmdsynopsis> + +</refsynopsisdiv> + +<refsect1 id='description'><title>DESCRIPTION</title> + +<para><application>bug-bugzilla</application> is a command-line tool +for posting bug reports to any instance of Bugzilla. It accepts on +standard input text resembling an RFC-822 message. The headers of +that message, and its body, are used to set error-report field values. +More field values are merged in from command-line options. If required +fields have not been set, <application>bug-bugzilla</application> +tries to compute them. Finally, the resulting error report is +validated. If all required fields are present, and there are no +illegal fields or values, the report is shipped off to the Mozilla +instance specified by the single positional argument. Login/password +credentials are read from the calling user's <filename>~/.netrc</filename> +file.</para> + +<para>The program accepts the following options to set or override fields:</para> +<variablelist> +<varlistentry> +<term>-b. --bug-status</term> +<listitem> +<para>Set the bug_status field, overriding the Status header from +standard input if present. (The stock Bugzilla web presentation +identifies this field as <quote>Status</quote>.)</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-u, --url</term> +<listitem> +<para>Set the bug_file_loc field, overriding the URL header from +standard input if present. (The stock Bugzilla web presentation +identifies this field as <quote>URL</quote>.)</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-p, --product</term> +<listitem> +<para>Set the product field, overriding the Product header from +standard input if necessary.</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-v, --version</term> +<listitem><para>Set the version field, overriding the Version header +from standard input if necessary.</para></listitem> +</varlistentry> +<varlistentry> +<term>-c, --component</term> +<listitem><para>Set the component field, overriding the Component header +from standard input if necessary.</para></listitem> +</varlistentry> +<varlistentry> +<term>-s, --summary</term> +<listitem><para>Set the short_desc field, overriding the Summary header +from standard input if necessary. (The stock Bugzilla web presentation +identifies this field as <quote>Summary</quote>.)</para></listitem> +</varlistentry> +<varlistentry> +<term>-H, --hardware</term> +<listitem><para>Set the rep_platform field, overriding the Hardware header +from standard input if necessary. (The stock Bugzilla web presentation +identifies this field as <quote>Hardware</quote>.)</para></listitem> +</varlistentry> +<varlistentry> +<term>-o, --os</term> +<listitem><para>Set the op_sys field, overriding the Operating-System header +from standard input if necessary. (The stock Bugzilla web presentation +identifies this field as <quote>OS</quote>.)</para></listitem> +</varlistentry> +<varlistentry> +<term>-r, --priority</term> +<listitem><para>Set the priority field, overriding the Priority header +from standard input if necessary.</para></listitem> +</varlistentry> +<varlistentry> +<term>-x, --severity</term> +<listitem><para>Set the severity field, overriding the Severity header +from standard input if necessary.</para></listitem> +</varlistentry> +<varlistentry> +<term>-d, --description</term> +<listitem><para>Set the comment field, overriding the Description header +from standard input if necessary. (The stock Bugzilla web presentation +identifies this field as <quote>Description</quote>.) If there is a +message body and no Description field and this option is not +specified, the message body is used as a description. +</para></listitem> +</varlistentry> +<varlistentry> +<term>-a, --assigned-to</term> +<listitem> +<para>Set the optional assigned_to field, overriding the Assigned-To +header from standard input if necessary.</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-C, --cc</term> +<listitem> +<para>Set the optional cc field, overriding the Cc +header from standard input if necessary.</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-k, --keywords</term> +<listitem> +<para>Set the optional keywords field, overriding the Keywords +header from standard input if necessary.</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-D, --depends-on</term> +<listitem> +<para>Set the optional dependson field, overriding the Depends-On +header from standard input if necessary.</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-B, --assigned-to</term> +<listitem> +<para>Set the optional blocked field, overriding the Blocked +header from standard input if necessary.</para> +</listitem> +</varlistentry> +<varlistentry> +<term>-n, --no-stdin</term> +<listitem><para>Suppress reading fields from standard input.</para></listitem> +</varlistentry> +<varlistentry> +<term>-h, --help</term> +<listitem><para>Print usage help and exit.</para></listitem> +</varlistentry> +</variablelist> + +<para>This program will try to deduce OS and Hardware if those are not +specified. If it fails, validation will fail before shipping the +report.</para> + +<para>There is expected to be a single positional argument following +any options. It should be the URL of the Bugzilla instance to which +the bug is to be submitted.</para> + +</refsect1> +<refsect1 id='files'><title>FILES</title> +<variablelist> +<varlistentry> +<term><filename>~/.netrc</filename></term> +<listitem><para>Must contain an entry in which the machine field is +the Bugzilla instance URL, the login field is your ID on that host, and the +password field is the right password. The URL in the machine field +must be enclosed in double quotes.</para></listitem> +</varlistentry> +</variablelist> + +</refsect1> +<refsect1 id='author'><title>AUTHORS</title> +<para>Christian Reis <kiko@async.com.br>, Eric S. Raymond +<esr@thyrsus.com>.</para> +</refsect1> +</refentry> + |