summaryrefslogtreecommitdiffstats
path: root/contrib/bugzilla-submit/bugzilla-submit
diff options
context:
space:
mode:
authorkiko%async.com.br <>2003-10-31 23:00:19 +0100
committerkiko%async.com.br <>2003-10-31 23:00:19 +0100
commit4d5799ba61bd0d5be1a7148c8b2fff6db1871a01 (patch)
treee8e20a55f278d04d4e2d6f74cf239d26722d7d92 /contrib/bugzilla-submit/bugzilla-submit
parent5a0e0a7735ff396d0a58b8c2d20b16cb0bcf6979 (diff)
downloadbugzilla-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/bugzilla-submit')
-rwxr-xr-xcontrib/bugzilla-submit/bugzilla-submit279
1 files changed, 279 insertions, 0 deletions
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)
+