diff --git a/bugzilla/__init__.py b/bugzilla/__init__.py deleted file mode 100644 index 1431b2f3..00000000 --- a/bugzilla/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -__all__ = ['models', 'utils', 'agents'] - -import httplib -from remoteobjects import http - -# Printing throws an error if we are printing using ascii -import sys -reload(sys) -sys.setdefaultencoding('utf-8') - -# Monkey patch remoteobjects to accept 202 status codes. -http.HttpObject.response_has_content[httplib.ACCEPTED] = False - - -VERSION = (0, 0, 1) -__version__ = '.'.join(map(str, VERSION)) diff --git a/bugzilla/agents.py b/bugzilla/agents.py deleted file mode 100644 index 7cd23514..00000000 --- a/bugzilla/agents.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -from bugzilla.models import BugSearch, Bug -from bugzilla.utils import urljoin, qs - - -class InvalidAPI_ROOT(Exception): - def __str__(self): - return "Invalid API url specified. " + \ - "Please set BZ_API_ROOT in your environment " + \ - "or pass it to the agent constructor" - - -class BugzillaAgent(object): - def __init__(self, api_root=None, api_key=None): - - if not api_root: - api_root = os.environ.get('BZ_API_ROOT') - if not api_root: - raise InvalidAPI_ROOT - self.API_ROOT = api_root - - self.api_key = api_key - - def get_bug(self, bug, include_fields='_default,token,cc,keywords,whiteboard,comments', exclude_fields=None, params={}): - params['include_fields'] = [include_fields] - params['exclude_fields'] = [exclude_fields] - - url = urljoin(self.API_ROOT, 'bug/%s?%s' % (bug, self.qs(**params))) - return Bug.get(url) - - def get_bug_list(self, params={}): - url = urljoin(self.API_ROOT, 'bug/?%s' % (self.qs(**params))) - return BugSearch.get(url).bugs - - def qs(self, **params): - if self.api_key: - params['api_key'] = [self.api_key] - return qs(**params) - - -class BMOAgent(BugzillaAgent): - def __init__(self, api_key=None): - super(BMOAgent, self).__init__('https://bugzilla.mozilla.org/bzapi/', api_key) diff --git a/bugzilla/fields.py b/bugzilla/fields.py deleted file mode 100644 index 389879b8..00000000 --- a/bugzilla/fields.py +++ /dev/null @@ -1,23 +0,0 @@ -from datetime import datetime - -from remoteobjects import fields -import dateutil.parser - - -class StringBoolean(fields.Field): - """Decodes a boolean hidden in a string.""" - - def decode(self, value): - return bool(int(value)) - - -class Datetime(fields.Datetime): - """Uses python-dateutil for working with datetimes.""" - - def decode(self, value): - return dateutil.parser.parse(value) - - def encode(self, value): - if not isinstance(value, datetime): - raise TypeError('Value to encode %r is not a datetime' % (value,)) - return value.replace(microsecond=0).strftime(self.dateformat) diff --git a/bugzilla/models.py b/bugzilla/models.py deleted file mode 100644 index 88030619..00000000 --- a/bugzilla/models.py +++ /dev/null @@ -1,235 +0,0 @@ -from remoteobjects import RemoteObject as RemoteObject_, fields - -from .fields import StringBoolean, Datetime - - -# The datetime format is inconsistent. -DATETIME_FORMAT_WITH_SECONDS = '%Y-%m-%d %H:%M:%S %z' -DATETIME_FORMAT = '%Y-%m-%d %H:%M %Z' - - -class RemoteObject(RemoteObject_): - - def post_to(self, url): - self._location = url - self.post(self) - return self.api_data['ref'] - - def put_to(self, url): - self._location = url - self.put() - - def _get_location(self): - if self.__location is not None: - return self.__location - else: - return self.api_data.get('ref', None) - - def _set_location(self, url): - self.__location = url - - _location = property(_get_location, _set_location) - - -class Bug(RemoteObject): - - id = fields.Field() - summary = fields.Field() - assigned_to = fields.Object('User') - creator = fields.Object('User') - target_milestone = fields.Field() - attachments = fields.List(fields.Object('Attachment')) - comments = fields.List(fields.Object('Comment')) - history = fields.List(fields.Object('Changeset')) - keywords = fields.List(fields.Object('Keyword')) - status = fields.Field() - resolution = fields.Field() - - # TODO: These are Mozilla specific and should be generalized - cf_blocking_20 = fields.Field() - cf_blocking_fennec = fields.Field() - cf_crash_signature = fields.Field() - - creation_time = Datetime(DATETIME_FORMAT_WITH_SECONDS) - flags = fields.List(fields.Object('Flag')) - blocks = fields.List(fields.Field()) - depends_on = fields.List(fields.Field()) - url = fields.Field() - cc = fields.List(fields.Object('User')) - keywords = fields.List(fields.Field()) - whiteboard = fields.Field() - - op_sys = fields.Field() - platform = fields.Field() - priority = fields.Field() - product = fields.Field() - qa_contact = fields.Object('User') - severity = fields.Field() - see_also = fields.List(fields.Field()) - version = fields.Field() - - alias = fields.Field() - classification = fields.Field() - component = fields.Field() - is_cc_accessible = StringBoolean() - is_everconfirmed = StringBoolean() - is_creator_accessible = StringBoolean() - last_change_time = Datetime(DATETIME_FORMAT_WITH_SECONDS) - ref = fields.Field() - - # Needed for submitting changes. - token = fields.Field(api_name='update_token') - - # Time tracking. - actual_time = fields.Field() - deadline = Datetime(DATETIME_FORMAT_WITH_SECONDS) - estimated_time = fields.Field() - # groups = fields.Field() # unimplemented - percentage_complete = fields.Field() - remaining_time = fields.Field() - work_time = fields.Field() - - def __repr__(self): - return '' % (self.id, self.summary) - - def __str__(self): - return "[Bug %s] - %s" % (self.id, self.summary) - - def __hash__(self): - return self.id - - -class User(RemoteObject): - - name = fields.Field() - real_name = fields.Field() - ref = fields.Field() - - def __repr__(self): - return '' % self.real_name - - def __str__(self): - return self.real_name or self.name - - def __hash__(self): - if not self or not self.name: - return 0 - return self.name.__hash__() - - -class Attachment(RemoteObject): - - # Attachment data. - id = fields.Field() - attacher = fields.Object('User') - creation_time = Datetime(DATETIME_FORMAT_WITH_SECONDS) - last_change_time = Datetime(DATETIME_FORMAT_WITH_SECONDS) - description = fields.Field() - bug_id = fields.Field() - bug_ref = fields.Field() - - # File data. - file_name = fields.Field() - size = fields.Field() - content_type = fields.Field() - - # Attachment metadata. - flags = fields.List(fields.Object('Flag')) - is_obsolete = StringBoolean() - is_private = StringBoolean() - is_patch = StringBoolean() - - # Used for submitting changes. - token = fields.Field() - ref = fields.Field() - - # Only with attachmentdata=1 - data = fields.Field() - encoding = fields.Field() - - def __repr__(self): - return '' % (self.id, self.description) - - def __hash__(self): - return self.id - - -class Comment(RemoteObject): - - id = fields.Field() - creator = fields.Object('User') - creation_time = Datetime(DATETIME_FORMAT_WITH_SECONDS) - text = fields.Field() - is_private = StringBoolean() - - def __repr__(self): - return '' % ( - self.creator, self.creation_time.strftime(DATETIME_FORMAT)) - - def __str__(self): - return self.text - - def __hash__(self): - return self.id - - -class Change(RemoteObject): - - field_name = fields.Field() - added = fields.Field() - removed = fields.Field() - - def __repr__(self): - return ' "%s">' % (self.field_name, self.removed, - self.added) - - -class Changeset(RemoteObject): - - changer = fields.Object('User') - changes = fields.List(fields.Object('Change')) - change_time = Datetime(DATETIME_FORMAT_WITH_SECONDS) - - def __repr__(self): - return '' % ( - self.changer, self.change_time.strftime(DATETIME_FORMAT)) - - -class Flag(RemoteObject): - - id = fields.Field() - name = fields.Field() - setter = fields.Object('User') - status = fields.Field() - requestee = fields.Object('User') - type_id = fields.Field() - - def __repr__(self): - return '' % self.name - - def __str__(self): - return self.name - - def __hash__(self): - return self.id - - -class Keyword(RemoteObject): - - name = fields.Field() - - def __repr__(self): - return '' % self.name - - def __str__(self): - return self.name - - def __hash__(self): - if not self or not self.name: - return 0 - return self.name.__hash__() - - -class BugSearch(RemoteObject): - - bugs = fields.List(fields.Object('Bug')) diff --git a/bugzilla/utils.py b/bugzilla/utils.py deleted file mode 100644 index f342630f..00000000 --- a/bugzilla/utils.py +++ /dev/null @@ -1,90 +0,0 @@ -import base64 -from ConfigParser import ConfigParser -import getpass -import os -import posixpath -import urllib - - -def urljoin(base, *args): - """Remove any leading slashes so no subpaths look absolute.""" - return posixpath.join(base, *[str(s).lstrip('/') for s in args]) - - -def qs(**kwargs): - """Build a URL query string.""" - url = '' - for k, v in kwargs.iteritems(): - if k == 'username' or k == 'password': - pass - for value in v: - url += '&%s=%s' % (urllib.quote(k), value) - return url - - -def get_credentials(username=None): - - # Try to get it from the environment first - if not username: - username = os.environ.get('BZ_USERNAME', None) - password = os.environ.get('BZ_PASSWORD', None) - - # Try to get it from the system keychain next - if not username and not password: - try: - import keyring - if not username: - # Grab the default username as we weren't passed in a specific one - username = keyring.get_password("bugzilla", 'default_username') - if username: - # Get the password for the username - password = keyring.get_password("bugzilla", username) - except ImportError: - # If they don't have the keyring lib, fall back to next method - pass - - # Then try a config file in their home directory - if not (username and password): - rcfile = os.path.expanduser('~/.bztoolsrc') - config = ConfigParser() - config.add_section('bugzilla') - if os.path.exists(rcfile): - try: - config.read(rcfile) - username = config.get('bugzilla', 'username') - _password = config.get('bugzilla', 'password') - if _password: - password = base64.b64decode(_password) - except Exception: - pass - - # Finally, prompt the user for the info if we didn't get it above - if not (username and password): - username = raw_input('Bugzilla username: ') - password = getpass.getpass('Bugzilla password: ') - try: - # Save the data to the keyring if possible - import keyring - keyring.set_password("bugzilla", 'default_username', username) - keyring.set_password("bugzilla", username, password) - except ImportError: - # Otherwise save it to a config file - config.set('bugzilla', 'username', username) - config.set('bugzilla', 'password', base64.b64encode(password)) - with open(rcfile, 'wb') as configfile: - config.write(configfile) - - return username, password - - -FILE_TYPES = { - 'text': 'text/plain', - 'html': 'text/html', - 'xml': 'application/xml', - 'gif': 'image/gif', - 'jpg': 'image/jpeg', - 'png': 'image/png', - 'svg': 'image/svg+xml', - 'binary': 'application/octet-stream', - 'xul': 'application/vnd.mozilla.xul+xml', -} diff --git a/scripts/__init__.py b/scripts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/scripts/attach.py b/scripts/attach.py deleted file mode 100644 index 435297d9..00000000 --- a/scripts/attach.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python - -import base64 -import itertools -import os -import argparse - -from bugzilla.models import Attachment, Flag, User, Comment -from bugzilla.agents import BugzillaAgent -from bugzilla.utils import urljoin, get_credentials, FILE_TYPES - -REVIEW = 4 - - -class AttachmentAgent(BugzillaAgent): - """Stores credentials, navigates the site.""" - - def attach(self, bug_id, filename, description, patch=False, - reviewer=None, comment='', content_type='text/plain'): - """Create an attachment, add a comment, obsolete other attachments.""" - - print 'Adding "%s" to %s' % (filename, bug_id) - self._attach(bug_id, filename, description, patch, - reviewer, content_type) - - bug = self.get_bug(bug_id) - - if comment: - print 'Adding the comment' - self._comment(bug_id, comment) - - print 'Finding attachments to make obsolete...' - self.obsolete(bug) - - def _attach(self, bug_id, filename, description, is_patch=False, - reviewer=None, content_type='text/plain'): - """Create a new attachment.""" - fields = { - 'data': base64.b64encode(open(filename).read()), - 'encoding': 'base64', - 'file_name': filename, - 'content_type': content_type, - 'description': description, - 'is_patch': is_patch, - } - - if reviewer is not None: - fields['flags'] = [Flag(type_id=REVIEW, status='?', - requestee=User(name=reviewer))] - - url = urljoin(self.API_ROOT, 'bug/%s/attachment?%s' % (bug_id, self.qs())) - return Attachment(**fields).post_to(url) - - def _comment(self, bug_id, comment): - """Create a new comment.""" - url = urljoin(self.API_ROOT, 'bug/%s/comment?%s' % (bug_id, self.qs())) - return Comment(text=comment).post_to(url) - - def obsolete(self, bug): - """Ask what attachments should be obsoleted.""" - attachments = [a for a in bug.attachments - if not bool(int(a.is_obsolete))] - - if not attachments: - return - - print "What attachments do you want to obsolete?" - msg = '[{index}] {a.id}: "{a.description}" ({a.file_name})' - for index, a in enumerate(attachments): - print msg.format(index=index, a=a) - - numbers = raw_input('Enter the numbers (space-separated) of ' - 'attachments to make obsolete:\n').split() - - if not numbers: - return - - map_ = dict((str(index), a) for index, a in enumerate(attachments)) - for num, _ in itertools.groupby(sorted(numbers)): - try: - self._obsolete(map_[num]) - except KeyError: - pass - - def _obsolete(self, attachment): - """Mark an attachment obsolete.""" - print "Obsoleting", attachment - attachment.is_obsolete = True - attachment._location += '?%s' % self.qs() - attachment.put() - - -def main(): - - # Script options - parser = argparse.ArgumentParser(description='Submit Bugzilla attachments') - - parser.add_argument('bug_id', - type=int, - metavar='BUG', - help='Bug number') - - parser.add_argument('filename', - metavar='FILE', - help='File to upload') - - parser.add_argument('--description', - help='Attachment description', - required=True) - - parser.add_argument('--patch', - action='store_true', - help='Is this a patch?') - - parser.add_argument('--reviewer', - help='Bugzilla name of someone to r?') - - parser.add_argument('--comment', - help='Comment for the attachment') - - parser.add_argument('--content_type', - choices=FILE_TYPES, - help="File's content_type") - - args = parser.parse_args() - - if args.content_type: - args.content_type = FILE_TYPES[args.content_type] - - # Get the API root, default to bugzilla.mozilla.org - API_ROOT = os.environ.get('BZ_API_ROOT', - 'https://api-dev.bugzilla.mozilla.org/latest/') - - # Authenticate - username, password = get_credentials() - - # Load the agent - bz = AttachmentAgent(API_ROOT, username, password) - - # Attach the file - bz.attach(**dict(args._get_kwargs())) - - -if __name__ == '__main__': - main() diff --git a/scripts/automated_release_emails.py b/scripts/automated_release_emails.py deleted file mode 100644 index f87af955..00000000 --- a/scripts/automated_release_emails.py +++ /dev/null @@ -1,92 +0,0 @@ -# Post-Mortem and Sign-off automated reminder -# Daily cronjob, 6am PT - -import urllib2 -import datetime -import re -import os -import json -import smtplib - -REPLY_TO_EMAIL = 'release-mgmt@mozilla.com' -SMTP = 'smtp.mozilla.org' -CONFIG_JSON = os.getcwd() + "/bztools/scripts/configs/config.json" -config = json.load(open(CONFIG_JSON, 'r')) -scripts_dir = os.getcwd() + "/scripts/" - -subject = None -toaddrs = ['dev-planning@lists.mozilla.org', 'release-drivers@mozilla.com'] - - -def sendMail(toaddr, options): - message = ( - "From: %s\r\n" % options['username'] - + "To: %s\r\n" % toaddr - + "CC: %s\r\n" % options['cclist'] - + "Reply-To: %s\r\n" % REPLY_TO_EMAIL - + "Subject: %s\r\n" % options['subject'] - + "\r\n" - + options['body']) - - server = smtplib.SMTP_SSL(SMTP, 465) - server.set_debuglevel(1) - server.login(options['username'], options['password']) - # note: toaddrs is required for transport agents, the msg['To'] header is not modified - server.sendmail(options['username'], toaddr, message) - server.quit() - - -def getTemplateValue(url): - version_regex = re.compile(".*

(.*)

.*") - template_page = urllib2.urlopen(url).read().replace('\n', '') - parsed_template = version_regex.match(template_page) - return parsed_template.groups()[0] - -# Grab the release date, the beta version number -release_date = getTemplateValue("https://wiki.mozilla.org/Template:FIREFOX_SHIP_DATE") -beta_version = getTemplateValue("https://wiki.mozilla.org/Template:BETA_VERSION") -current_version = getTemplateValue("https://wiki.mozilla.org/Template:CURRENT_VERSION") -today = datetime.date.today() -release = datetime.datetime.strptime(release_date, "%B %d, %Y").date() - -# Check the timedelta between today and releasedate and if: -# -7 days before release date Sign Off reminder for 'tomorrow': Thurs at 10am PT -# -29 days before next release date send Post-Mortem for the previous version 'tomorrow': Tues at 10am PT) -timedelta = today - release - -if timedelta.days == -7: - # send the reminder email for sign off meeting - print "Sending Sign-off email reminder %s" % today - subject = "Automatic Reminder: Firefox %s Sign Off Meeting" % beta_version - body = """ -This is a reminder that the FF%s sign-off meeting will be held tomorrow in the Release Coordination Vidyo room @ 10:00 am PT. - -The wiki page is up and ready for you to add notes : https://wiki.mozilla.org/Releases/Firefox_%s/Final_Signoffs - --- Release Management -""" % (beta_version, beta_version) -if timedelta.days == -29: - # send the reminder email for post-mortem of curent release version - print "Sending post-mortem email reminder %s" % today - subject = "Reminder: Firefox %s Post Mortem Meeting Tomorrow" % current_version - body = """ -Friendly Reminder that the FF%s.0 Post-Mortem will take place tomorrow @ 10:00 am PT during the Channel Meeting in the Release Co-ordination Vidyo room. - -Etherpad - https://etherpad.mozilla.org/%s-0-Post-Mortem - --- Release Management -""" % (current_version, current_version) - -if subject is not None: - options = { - "username": config['ldap_username'], - "password": config['ldap_password'], - "subject": subject, - "body": body, - "cclist": "release-mgmt@mozilla.com", - "toaddrs": toaddrs - } - for email in toaddrs: - sendMail(email, options) -else: - print "No command today: %s" % today diff --git a/scripts/b2g_query_creator.py b/scripts/b2g_query_creator.py deleted file mode 100755 index e995fa5d..00000000 --- a/scripts/b2g_query_creator.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/python - -import datetime -import subprocess -import os -import json -from argparse import ArgumentParser - -CONFIG_JSON = os.getcwd() + "/scripts/configs/config.json" -config = json.load(open(CONFIG_JSON, 'r')) -scripts_dir = os.getcwd() + "/scripts/" -queries_dir = os.getcwd() + "/queries/" - - -def createQuery(title, short_title, url, show_summary, cc): - file_name = queries_dir + str(datetime.date.today()) + '_' + short_title - - qf = open(file_name, 'w') - qf.write("query_name = \'" + title + "\'\n") - qf.write("query_url = \'" + url + "\'\n") - qf.write("show_summary = \'" + str(show_summary) + "\'\n") - if cc is not None: - qf.write("cc = \'" + ",".join(cc) + "\'\n") - - return file_name - - -def createQueriesList(print_all): - queries = [] - weekday = datetime.datetime.today().weekday() - for url in urls: - try: - cc = url[1][4] - except IndexError: - cc = None # no cc - # Every Weekday - if weekday >= 0 and weekday < 5 and url[0] == 5: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3], cc=cc)) - # Monday only emails - if weekday == 0 and url[0] == 0: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3], cc=cc)) - # Tuesday only emails - if weekday == 1 and url[0] == 1: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3], cc=cc)) - # Thursday only emails - if weekday == 3 and url[0] == 3: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3], cc=cc)) - # Friday only emails - if weekday == 4 and url[0] == 4: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3], cc=cc)) - return queries - -# ========================= CURRENT QUERIES ============================ -# NOTE: You must replace query bug_status with comma-separated values -# eg: &bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED -# -# ==== KOI (v1.2) ==== -unfixed_koi_sec_url = "https://bugzilla.mozilla.org/buglist.cgi?f1=cf_blocking_b2g&o1=equals&resolution=---&bug_status=UNCONFIRMED,NEW,READY,ASSIGNED,REOPENED&v1=koi%2B&f2=status_whiteboard&o2=notsubstring&v2=[no-nag]&o3=anywordssubstr&v3=Confidential%20Security&f3=bug_group" -unfixed_koi_url = "https://bugzilla.mozilla.org/buglist.cgi?f1=cf_blocking_b2g&o1=equals&resolution=---&bug_status=UNCONFIRMED,NEW,READY,ASSIGNED,REOPENED&v1=koi%2B&f2=status_whiteboard&o2=notsubstring&v2=[no-nag]&o3=anywordssubstr&v3=Confidential%20Security&f3=bug_group&n3=1" - -team_dev_koi_nom = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=DOM%3A%20Device%20Interfaces&v2=koi%3F&product=Core&o5=equals&n5=1&v5=FIXED&f5=resolution" -team_dev_koi_blockers = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=DOM%3A%20Device%20Interfaces&v2=koi%2B&product=Core&o5=equals&n5=1&v5=FIXED&f5=resolution&o6=equals&n6=1&v6=WORKSFORME&f6=resolution&o7=equals&n7=1&v7=DUPLICATE&f7=resolution&o8=equals&n8=1&v8=INVALID&f8=resolution&o9=equals&n9=1&v9=WONTFIX&f9=resolution" - -team_gfx_koi_nom = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=Graphics%3A%20Layers&v2=koi%3F&product=Core&o5=equals&n5=1&v5=FIXED&f5=resolution" -team_gfx_koi_blockers = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=Graphics%3A%20Layers&v2=koi%2B&product=Core&o5=equals&n5=1&v5=FIXED&f5=resolution&o6=equals&n6=1&v6=WORKSFORME&f6=resolution&o7=equals&n7=1&v7=DUPLICATE&f7=resolution&o8=equals&n8=1&v8=INVALID&f8=resolution&o9=equals&n9=1&v9=WONTFIX&f9=resolution" - -team_media_koi_nom = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=Gaia%3A%3ACamera&component=Gaia%3A%3AFMRadio&component=Gaia%3A%3AMusic&component=Gaia%3A%3AVideo&v2=koi%3F&product=Firefox%20OS&o5=equals&n5=1&v5=FIXED&f5=resolution&o6=equals&n6=1&v6=WORKSFORME&f6=resolution&o7=equals&n7=1&v7=DUPLICATE&f7=resolution&o8=equals&n8=1&v8=INVALID&f8=resolution&o9=equals&n9=1&v9=WONTFIX&f9=resolution" -team_media_koi_blockers = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=Gaia%3A%3ACamera&component=Gaia%3A%3AFMRadio&component=Gaia%3A%3AMusic&component=Gaia%3A%3AVideo&v2=koi%2B&product=Firefox%20OS&o5=equals&n5=1&v5=FIXED&f5=resolution&o6=equals&n6=1&v6=WORKSFORME&f6=resolution&o7=equals&n7=1&v7=DUPLICATE&f7=resolution&o8=equals&n8=1&v8=INVALID&f8=resolution&o9=equals&n9=1&v9=WONTFIX&f9=resolution" - -team_comm_koi_nom = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=Gaia%3A%3AContacts&component=Gaia%3A%3ADialer&component=Gaia%3A%3AEverything.me&component=Gaia%3A%3ASMS&v2=koi%3F&product=Firefox%20OS&o5=equals&n5=1&v5=FIXED&f5=resolution" -team_comm_koi_blockers = "https://bugzilla.mozilla.org/buglist.cgi?f1=OP&f0=OP&o2=equals&f4=CP&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED,NEW,ASSIGNED,REOPENED&component=Gaia%3A%3AContacts&component=Gaia%3A%3ADialer&component=Gaia%3A%3AEverything.me&component=Gaia%3A%3ASMS&v2=koi%2B&product=Firefox%20OS&o5=equals&n5=1&v5=FIXED&f5=resolution&o6=equals&n6=1&v6=WORKSFORME&f6=resolution&o7=equals&n7=1&v7=DUPLICATE&f7=resolution&o8=equals&n8=1&v8=INVALID&f8=resolution&o9=equals&n9=1&v9=WONTFIX&f9=resolution" - -koi_regressions_unfixed = "https://bugzilla.mozilla.org/buglist.cgi?j_top=OR&keywords=regression%2C%20regressionwindow-wanted%2C%20&keywords_type=anywords&f1=cf_blocking_b2g&o1=anywords&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&v1=koi" -koi_assignee_no_comment_three_days = "https://bugzilla.mozilla.org/buglist.cgi?o5=nowordssubstr&f1=OP&f0=OP&f8=owner_idle_time&o2=equals&f4=OP&v5=fixed%20verified%20unaffected%20wontfix&j1=OR&f3=CP&f2=cf_blocking_b2g&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&j4=OR&f5=cf_status_b2g_1_2&v8=-3d&f6=CP&v2=koi%2B&f7=CP&o8=greaterthan" - -# ==== General ==== -needinfo_sec_blockers_url = "https://bugzilla.mozilla.org/buglist.cgi?o1=anywords&o2=substring&v1=koi%2B&v2=needinfo%3F&f1=cf_blocking_b2g&resolution=---&f2=flagtypes.name&f3=status_whiteboard&o3=notsubstring&v3=[no-nag]&o4=anywordssubstr&v4=Confidential%20Security&f4=bug_group" -needinfo_blockers_url = "https://bugzilla.mozilla.org/buglist.cgi?o1=anywords&o2=substring&v1=koi%2B&v2=needinfo%3F&f1=cf_blocking_b2g&resolution=---&f2=flagtypes.name&f3=status_whiteboard&o3=notsubstring&v3=[no-nag]&o4=anywordssubstr&v4=Confidential%20Security&f4=bug_group&n4=1" - -# TODO - sort the queries according to a priority flag -# TODO - batch up by query name (so sec & non-sec get in the same output) -urls = [ - (5, ["Blocker Bugs with Need-Info? (Sec)", "needinfo_sec_blockers", needinfo_sec_blockers_url, 0]), - (5, ["Blocker Bugs with Need-Info?", "needinfo_blockers", needinfo_blockers_url, 1]), - (0, ["Bugs Blocking KOI (Sec)", "unfixed_koi_sec", unfixed_koi_sec_url, 0]), - (0, ["Bugs Blocking KOI", "unfixed_koi", unfixed_koi_url, 1]), - (1, ["Koi Blocking Nominiations for DOM: Dev Interfaces", "team_dev_koi_nom", team_dev_koi_nom, 1, ["dhylands@mozilla.com", ]]), - (1, ["Koi Blocker Bugs, DOM: Dev Interfaces", "team_dev_koi_blockers", team_dev_koi_blockers, 1, ["dhylands@mozilla.com", ]]), - (1, ["Koi Blocking Nominiations for Graphics", "team_gfx_koi_nom", team_gfx_koi_nom, 1, ["msreckovic@mozilla.com", ]]), - (1, ["Koi Blocker Bugs, Graphics", "team_gfx_koi_blockers", team_gfx_koi_blockers, 1, ["msreckovic@mozilla.com", ]]), - (1, ["Koi Blocking Nominiations for B2G: Media", "team_media_koi_nom", team_media_koi_nom, 1, ["hkoka@mozilla.com", ]]), - (1, ["Koi Blocker Bugs, B2G: Media", "team_media_koi_blockers", team_media_koi_blockers, 1, ["hkoka@mozilla.com", ]]), - (1, ["Koi Blocking Nominiations for B2G: Communications", "team_comm_koi_nom", team_comm_koi_nom, 1, ["dscravaglieri@mozilla.com", ]]), - (1, ["Koi Blocker Bugs, B2G: Communications", "team_comm_koi_blockers", team_comm_koi_blockers, 1, ["dscravaglieri@mozilla.com", ]]), - (4, ["Koi Blocker Bugs, No Comment from Assignee in 3 days or more", "koi_assignee_no_comment_three_days", koi_assignee_no_comment_three_days, 1]), - (4, ["Koi Blocker Bugs, Regressions", "koi_regressions_unfixed", koi_regressions_unfixed, 1]), -] - - -def cleanUp(): - for file in os.listdir(queries_dir): - if file.startswith(str(datetime.date.today())): - os.remove(os.path.join(queries_dir, file)) - -if __name__ == '__main__': - parser = ArgumentParser(__doc__) - parser.set_defaults( - queries_only=False, - ) - parser.add_argument("-q", "--queries-only", dest="queries_only", action="store_true", - help="just create and print queries") - - options, args = parser.parse_known_args() - queries = createQueriesList(print_all=options.queries_only) - - if options.queries_only: - for url in urls: - print url - else: - command = [ - scripts_dir + "email_nag.py", - "-t", "daily_b2g_email", - "--no-verification", - "-m", config['ldap_username'], - "-p", config['ldap_password']] - for query in queries: - command.append('-q') - command.append(query) - if ('-r') in args: - subject = datetime.datetime.today().strftime("%A %b %d") + " -- Daily B2G Drivers Rollup" - command.extend(['-e', 'b2g-release-drivers@mozilla.org']) - else: - subject = datetime.datetime.today().strftime("%A %b %d") + " -- Daily B2G Blocking Bugs Alert" - command.extend(['-s', subject]) - # send all other args to email_nag script argparser - command.extend(args) - subprocess.call(command) - cleanUp() diff --git a/scripts/email_nag.py b/scripts/email_nag.py deleted file mode 100755 index ef2fdf59..00000000 --- a/scripts/email_nag.py +++ /dev/null @@ -1,474 +0,0 @@ -#!/usr/bin/env python -""" - -A script for automated nagging emails based on passed in queries -These can be collated into several 'queries' through the use of multiple query files with -a 'query_name' param set eg: 'Bugs tracked for Firefox Beta (13)' -Once the bugs have been collected from Bugzilla they are sorted into buckets cc: assignee manager -and to the assignee(s) or need-info? for each query - -""" -import sys -import os -import smtplib -import subprocess -import tempfile -import collections -from datetime import datetime -from argparse import ArgumentParser -from bugzilla.agents import BMOAgent -import phonebook -from jinja2 import Environment, FileSystemLoader -env = Environment(loader=FileSystemLoader('templates')) - -REPLY_TO_EMAIL = 'release-mgmt@mozilla.com' -DEFAULT_CC = ['release-mgmt@mozilla.com'] -EMAIL_SUBJECT = '' -SMTP = 'smtp.mozilla.org' -people = phonebook.PhonebookDirectory() - -# TODO - Sort by who a bug is blocked on (thanks @dturner) -# TODO - write tests! -# TODO - look into knocking out duplicated bugs in queries -- perhaps print out if there are dupes in queries when queries > 1 -# TODO - should compare bugmail from API results to phonebook bugmail in to_lower() - - -def get_last_manager_comment(comments, manager): - # go through in reverse order to get most recent - for comment in comments[::-1]: - if person is not None: - if comment.creator.name == manager['mozillaMail'] or comment.creator.name == manager['bugzillaEmail']: - return comment.creation_time.replace(tzinfo=None) - return None - - -def get_last_assignee_comment(comments, person): - # go through in reverse order to get most recent - for comment in comments[::-1]: - if person is not None: - if comment.creator.name == person['mozillaMail'] or comment.creator.name == person['bugzillaEmail']: - return comment.creation_time.replace(tzinfo=None) - return None - - -def query_url_to_dict(url): - if (';')in url: - fields_and_values = url.split("?")[1].split(";") - else: - fields_and_values = url.split("?")[1].split("&") - d = collections.defaultdict(list) - - for pair in fields_and_values: - (key, val) = pair.split("=") - if key != "list_id": - d[key].append(val) - return d - - -def generateEmailOutput(subject, queries, template, show_comment=False, manager_email=None, rollup=False, rollupEmail=None): - cclist = [] - toaddrs = [] - template_params = {} - # stripping off the templates dir, just in case it gets passed in the args - template = env.get_template(template.replace('templates/', '', 1)) - - def addToAddrs(bug): - if bug.assigned_to.name in people.people_by_bzmail: - person = dict(people.people_by_bzmail[bug.assigned_to.name]) - if person['mozillaMail'] not in toaddrs: - toaddrs.append(person['mozillaMail']) - - for query in queries.keys(): - # Avoid dupes in the cclist from several queries - query_cc = queries[query].get('cclist', []) - for qcc in query_cc: - if qcc not in cclist: - cclist.append(qcc) - if query not in template_params: - template_params[query] = {'buglist': []} - if len(queries[query]['bugs']) != 0: - for bug in queries[query]['bugs']: - if 'show_summary' in queries[query]: - if queries[query]['show_summary'] == '1': - summary = bug.summary - else: - summary = "" - else: - summary = "" - template_params[query]['buglist'].append( - { - 'id': bug.id, - 'summary': summary, - # 'comment': bug.comments[-1].creation_time.replace(tzinfo=None), - 'assignee': bug.assigned_to.real_name, - 'flags': bug.flags - } - ) - # more hacking for JS special casing - if bug.assigned_to.name == 'general@js.bugs' and 'nihsanullah@mozilla.com' not in toaddrs: - toaddrs.append('nihsanullah@mozilla.com') - # if needinfo? in flags, add the flag.requestee to the toaddrs instead of bug assignee - if bug.flags: - for flag in bug.flags: - if 'needinfo' in flag.name and flag.status == '?': - try: - person = dict(people.people_by_bzmail[str(flag.requestee)]) - if person['mozillaMail'] not in toaddrs: - toaddrs.append(person['mozillaMail']) - except: - if str(flag.requestee) not in toaddrs: - toaddrs.append(str(flag.requestee)) - else: - addToAddrs(bug) - else: - addToAddrs(bug) - - message_body = template.render(queries=template_params, show_comment=show_comment) - if manager_email is not None and manager_email not in cclist: - cclist.append(manager_email) - # no need to and cc the manager if more than one email - if len(toaddrs) > 1: - for email in toaddrs: - if email in cclist: - toaddrs.remove(email) - - if cclist == ['']: - cclist = None - if rollup: - joined_to = ",".join(rollupEmail) - else: - joined_to = ",".join(toaddrs) - message = ( - "From: %s\r\n" % REPLY_TO_EMAIL - + "To: %s\r\n" % joined_to - + "CC: %s\r\n" % ",".join(cclist) - + "Subject: %s\r\n" % subject - + "\r\n" - + message_body) - - toaddrs = toaddrs + cclist - - return toaddrs, message - - -def sendMail(toaddrs, msg, username, password, dryrun=False): - if dryrun: - print "\n****************************\n* DRYRUN: not sending mail *\n****************************\n" - print msg - else: - server = smtplib.SMTP_SSL(SMTP, 465) - server.set_debuglevel(1) - server.login(username, password) - # note: toaddrs is required for transport agents, the msg['To'] header is not modified - server.sendmail(username, toaddrs, msg) - server.quit() - - -if __name__ == '__main__': - parser = ArgumentParser(__doc__) - parser.set_defaults( - dryrun=False, - username=None, - password=None, - roll_up=False, - show_comment=False, - email_cc_list=None, - queries=[], - days_since_comment=-1, - verbose=False, - keywords=None, - email_subject=None, - no_verification=False, - ) - parser.add_argument("-d", "--dryrun", dest="dryrun", action="store_true", - help="just do the query, and print emails to console without emailing anyone") - parser.add_argument("-m", "--mozilla-email", dest="mozilla_mail", - help="specify a specific address for sending email"), - parser.add_argument("-p", "--email-password", dest="email_password", - help="specify a specific password for sending email") - parser.add_argument("-b", "--bz-api-key", dest="bz_api_key", - help="Bugzilla API key") - parser.add_argument("-t", "--template", dest="template", - required=True, - help="template to use for the buglist output") - parser.add_argument("-e", "--email-cc-list", dest="email_cc_list", - action="append", - help="email addresses to include in cc when sending mail") - parser.add_argument("-q", "--query", dest="queries", - action="append", - required=True, - help="a file containing a dictionary of a bugzilla query") - parser.add_argument("-k", "--keyword", dest="keywords", - action="append", - help="keywords to collate buglists") - parser.add_argument("-s", "--subject", dest="email_subject", - required=True, - help="The subject of the email being sent") - parser.add_argument("-r", "--roll-up", dest="roll_up", action="store_true", - help="flag to get roll-up output in one email instead of creating multiple emails") - parser.add_argument("--show-comment", dest="show_comment", action="store_true", - help="flag to display last comment on a bug in the message output") - parser.add_argument("--days-since-comment", dest="days_since_comment", - help="threshold to check comments against to take action based on days since comment") - parser.add_argument("--verbose", dest="verbose", action="store_true", - help="turn on verbose output") - parser.add_argument("--no-verification", dest="no_verification", action="store_true", - help="don't wait for human verification of every email") - - options, args = parser.parse_known_args() - - try: - int(options.days_since_comment) - except: - if options.days_since_comment is not None: - parser.error("Need to provide a number for days \ - since last comment value") - if options.email_cc_list is None: - options.email_cc_list = DEFAULT_CC - - # Load our agent for BMO - bmo = BMOAgent(options.bz_api_key) - - # Get the buglist(s) - collected_queries = {} - for query in options.queries: - # import the query - if os.path.exists(query): - info = {} - execfile(query, info) - query_name = info['query_name'] - if query_name not in collected_queries: - collected_queries[query_name] = { - 'channel': info.get('query_channel', ''), - 'bugs': [], - 'show_summary': info.get('show_summary', 0), - 'cclist': options.email_cc_list, - } - if 'cc' in info: - for c in info.get('cc').split(','): - collected_queries[query_name]['cclist'].append(c) - if 'query_params' in info: - print "Gathering bugs from query_params in %s" % query - collected_queries[query_name]['bugs'] = bmo.get_bug_list(info['query_params']) - elif 'query_url' in info: - print "Gathering bugs from query_url in %s" % query - collected_queries[query_name]['bugs'] = bmo.get_bug_list(query_url_to_dict(info['query_url'])) - # print "DEBUG: %d bug(s) found for query %s" % \ - # (len(collected_queries[query_name]['bugs']), info['query_url']) - else: - print "Error - no valid query params or url in the config file" - sys.exit(1) - else: - print "Not a valid path: %s" % query - total_bugs = 0 - for channel in collected_queries.keys(): - total_bugs += len(collected_queries[channel]['bugs']) - - print "Found %s bugs total for %s queries" % (total_bugs, len(collected_queries.keys())) - print "Queries to collect: %s" % collected_queries.keys() - - managers = people.managers - manual_notify = {} - counter = 0 - - def add_to_managers(manager_email, query, info={}): - if manager_email not in managers: - managers[manager_email] = {} - managers[manager_email]['nagging'] = {query: {'bugs': [bug], - 'show_summary': info.get('show_summary', 0), - 'cclist': info.get('cclist', [])}, } - return - if 'nagging' in managers[manager_email]: - if query in managers[manager_email]['nagging']: - managers[manager_email]['nagging'][query]['bugs'].append(bug) - if options.verbose: - print "Adding %s to %s in nagging for %s" % \ - (bug.id, query, manager_email) - else: - managers[manager_email]['nagging'][query] = { - 'bugs': [bug], - 'show_summary': info.get('show_summary', 0), - 'cclist': info.get('cclist', []) - } - if options.verbose: - print "Adding new query key %s for bug %s in nagging \ - and %s" % (query, bug.id, manager_email) - else: - managers[manager_email]['nagging'] = {query: {'bugs': [bug], - 'show_summary': info.get('show_summary', 0), - 'cclist': info.get('cclist', [])}, } - if options.verbose: - print "Creating query key %s for bug %s in nagging and \ - %s" % (query, bug.id, manager_email) - - for query, info in collected_queries.items(): - if len(collected_queries[query]['bugs']) != 0: - manual_notify[query] = {'bugs': [], 'show_summary': info.get('show_summary', 0)} - for b in collected_queries[query]['bugs']: - counter = counter + 1 - send_mail = True - bug = bmo.get_bug(b.id) - manual_notify[query]['bugs'].append(bug) - assignee = bug.assigned_to.name - if "@" not in assignee: - print "Error - email address expect. Found '" + assignee + "' instead" - print "Check that the authentication worked correctly" - sys.exit(1) - if assignee in people.people_by_bzmail: - person = dict(people.people_by_bzmail[assignee]) - else: - person = None - - # kick bug out if days since comment check is on - if options.days_since_comment != -1: - # try to get last_comment by assignee & manager - if person is not None: - last_comment = get_last_assignee_comment(bug.comments, person) - if 'manager' in person and person['manager'] is not None: - manager_email = person['manager']['dn'].split('mail=')[1].split(',')[0] - manager = people.people[manager_email] - last_manager_comment = get_last_manager_comment(bug.comments, people.people_by_bzmail[manager['bugzillaEmail']]) - # set last_comment to the most recent of last_assignee and last_manager - if last_manager_comment is not None and last_comment is not None and last_manager_comment > last_comment: - last_comment = last_manager_comment - # otherwise just get the last comment - else: - last_comment = bug.comments[-1].creation_time.replace(tzinfo=None) - if last_comment is not None: - timedelta = datetime.now() - last_comment - if timedelta.days <= int(options.days_since_comment): - if options.verbose: - print "Skipping bug %s since it's had an assignee or manager comment within the past %s days" % (bug.id, options.days_since_comment) - send_mail = False - counter = counter - 1 - manual_notify[query]['bugs'].remove(bug) - else: - if options.verbose: - print "This bug needs notification, it's been %s since last comment of note" % timedelta.days - - if send_mail: - if 'nobody' in assignee: - if options.verbose: - print "No one assigned to: %s, will be in the manual notification list..." % bug.id - # TODO - get rid of this, SUCH A HACK! - elif 'general@js.bugs' in assignee: - if options.verbose: - print "No one assigned to JS bug: %s, adding to Naveed's list..." % bug.id - add_to_managers('nihsanullah@mozilla.com', query, info) - else: - if bug.assigned_to.real_name is not None: - if person is not None: - # check if assignee is already a manager, add to their own list - if 'mozillaMail' in managers: - add_to_managers(person['mozillaMail'], query, info) - # otherwise we search for the assignee's manager - else: - # check for manager key first, a few people don't have them - if 'manager' in person and person['manager'] is not None: - manager_email = person['manager']['dn'].split('mail=')[1].split(',')[0] - if manager_email in managers: - add_to_managers(manager_email, query, info) - elif manager_email in people.vices: - # we're already at the highest level we'll go - if assignee in managers: - add_to_managers(assignee, query, info) - else: - if options.verbose: - print "%s has a V-level for a manager, and is not in the manager list" % assignee - managers[person['mozillaMail']] = {} - add_to_managers(person['mozillaMail'], query, info) - else: - # try to go up one level and see if we find a manager - if manager_email in people.people: - person = dict(people.people[manager_email]) - manager_email = person['manager']['dn'].split('mail=')[1].split(',')[0] - if manager_email in managers: - add_to_managers(manager_email, query, info) - else: - print "Manager could not be found: %s" % manager_email - # if you don't have a manager listed, but are an employee, we'll nag you anyway - else: - add_to_managers(person['mozillaMail'], query, info) - print "%s's entry doesn't list a manager! Let's ask them to update phonebook but in the meantime they get the email directly." % person['name'] - - if options.roll_up: - # only send one email - toaddrs, msg = generateEmailOutput(subject=options.email_subject, - queries=manual_notify, - template=options.template, - show_comment=options.show_comment, - rollup=options.roll_up, - rollupEmail=options.email_cc_list) - if options.email_password is None or options.mozilla_mail is None: - print "Please supply a username/password (-m, -p) for sending email" - sys.exit(1) - if not options.dryrun: - print "SENDING EMAIL" - sendMail(toaddrs, msg, options.mozilla_mail, options.email_password, options.dryrun) - else: - # Get yr nag on! - for email, info in managers.items(): - inp = '' - if 'nagging' in info: - toaddrs, msg = generateEmailOutput( - subject=options.email_subject, - manager_email=email, - queries=info['nagging'], - template=options.template, - show_comment=options.show_comment) - while True and not options.no_verification: - print "\nRelMan Nag is ready to send the following email:\n<------ MESSAGE BELOW -------->" - print msg - print "<------- END MESSAGE -------->\nWould you like to send now?" - inp = raw_input('\n Please select y/Y to send, v/V to edit, or n/N to skip and continue to next email: ') - - if inp != 'v' and inp != 'V': - break - - tempfilename = tempfile.mktemp() - temp_file = open(tempfilename, 'w') - temp_file.write(msg) - temp_file.close() - - subprocess.call(['vi', tempfilename]) - - temp_file = open(tempfilename, 'r') - msg = temp_file.read() - toaddrs = msg.split("To: ")[1].split("\r\n")[0].split(',') + msg.split("CC: ")[1].split("\r\n")[0].split(',') - os.remove(tempfilename) - - if inp == 'y' or inp == 'Y' or options.no_verification: - if options.email_password is None or options.mozilla_mail is None: - print "Please supply a username/password (-m, -p) for sending email" - sys.exit(1) - if not options.dryrun: - print "SENDING EMAIL" - sendMail(toaddrs, msg, options.mozilla_mail, options.email_password, options.dryrun) - sent_bugs = 0 - for query, info in info['nagging'].items(): - sent_bugs += len(info['bugs']) - # take sent bugs out of manual notification list - for bug in info['bugs']: - manual_notify[query]['bugs'].remove(bug) - counter = counter - sent_bugs - -if not options.roll_up: - emailed_bugs = [] - # Send RelMan the manual notification list only when there are bugs that didn't go out - msg_body = """\n******************************************\nNo nag emails were generated for these bugs because -they are either assigned to no one or to non-employees (though ni? on non-employees will get nagged). -\nYou will need to look at the following bugs:\n******************************************\n\n""" - for k, v in manual_notify.items(): - if len(v['bugs']) != 0: - for bug in v['bugs']: - if bug.id not in emailed_bugs: - if k not in msg_body: - msg_body += "\n=== %s ===\n" % k - emailed_bugs.append(bug.id) - msg_body += "http://bugzil.la/" + "%s -- assigned to: %s\n -- Last commented on: %s\n" % (bug.id, bug.assigned_to.real_name, bug.comments[-1].creation_time.replace(tzinfo=None)) - msg = ("From: %s\r\n" % REPLY_TO_EMAIL - + "To: %s\r\n" % REPLY_TO_EMAIL - + "Subject: RelMan Attention Needed: %s\r\n" % options.email_subject - + "\r\n" - + msg_body) - sendMail(['release-mgmt@mozilla.com'], msg, options.mozilla_mail, options.email_password, options.dryrun) diff --git a/scripts/phonebook.py b/scripts/phonebook.py deleted file mode 100644 index 28da6d9d..00000000 --- a/scripts/phonebook.py +++ /dev/null @@ -1,77 +0,0 @@ -import requests -import os -import json - -# NOTE: You must create a file for CONFIG_JSON with your LDAP auth in it like: -# { -# "username": "username", -# "password": "password" -# } -# -# In order to access the phonebook data - - -MY_DIR = os.path.abspath(os.path.dirname(__file__)) -PEOPLE_FILENAME = os.path.join(MY_DIR, 'people.json') -CONFIG_JSON = os.path.join(os.path.dirname(__file__), "configs/config.json") -BASE_URL = 'https://phonebook.mozilla.org' -PEOPLE_URL = '%s/search.php?query=*&format=fligtar' % BASE_URL - -''' -a single phonebook entry data looks like this when you pull it from JSON: -'email' = { - ims : [], - name : 'name', - title : 'title', - phones : 'string of numbers & assignments', - ext : XXX, - manager : {u'dn': u'mail=manager@mozilla.com,o=com,dc=mozilla', u'cn': u'Manager Name'}, - bugzillaEmail : 'email@example.com' - - ## this script adds in: - mozillaMail : 'email@mozilla.com' - } -''' - - -class PhonebookDirectory(): - - def __init__(self, config=CONFIG_JSON): - config = json.load(open(config, 'r')) - print "Fetching people from phonebook..." - self.people = json.loads(requests.get(PEOPLE_URL, auth=(config['ldap_username'], config['ldap_password'])).content) - self.people_by_bzmail = self.get_people_by_bzmail() - self.managers = self.get_managers() - self.vices = self.get_vices() - - def get_managers(self): - managers = {} - for email, info in self.people.items(): - if self.people[email]['title'] is not None: - if 'director' in self.people[email]['title'].lower() or 'manager' in self.people[email]['title'].lower(): - managers[email] = info - # HACK! don't have titles with manager/director or missing bugmail address - if email in ('dtownsend@mozilla.com', 'dougt@mozilla.com', 'mfinkle@mozilla.com', 'bsmedberg@mozilla.com', 'blassey@mozilla.com') and email not in managers.keys(): - managers[email] = info - return managers - - def get_vices(self): - vices = {} - for email, info in self.people.items(): - if self.people[email]['title'] is not None: - if 'vice' in self.people[email]['title'].lower(): - vices[email] = info - return vices - - def get_people_by_bzmail(self): - temp = {} - for email, info in self.people.items(): - # if someone doesn't have a bugzillaEmail set, we'll try their mozilla mail instead - if info.get('bugzillaEmail'): - temp[info['bugzillaEmail']] = dict(info.items()) - temp[info['bugzillaEmail']].update({'mozillaMail': email}) - else: - temp[email] = dict(info.items()) - temp[email].update({'mozillaMail': email}) - - return temp diff --git a/scripts/query_creator.py b/scripts/query_creator.py deleted file mode 100755 index 267ecd65..00000000 --- a/scripts/query_creator.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/python - -import urllib2 -import re -import datetime -import subprocess -import os -import json -from argparse import ArgumentParser - -CONFIG_JSON = os.getcwd() + "/scripts/configs/config.json" -config = json.load(open(CONFIG_JSON, 'r')) -scripts_dir = os.getcwd() + "/scripts/" -queries_dir = os.getcwd() + "/queries/" - - -def getTemplateValue(url): - version_regex = re.compile(".*

(.*)

.*") - template_page = urllib2.urlopen(url).read().replace('\n', '') - parsed_template = version_regex.match(template_page) - return parsed_template.groups()[0] - - -def getReportURL(approval_flag, span): - a = urllib2.urlopen("https://bugzilla.mozilla.org/page.cgi?id=release_tracking_report.html&q=" + approval_flag + "%3A%2B%3A" + span + "%3A0%3Aand%3A") - return a.url - -no_nag = ";field3-1-0=status_whiteboard;type3-1-0=notsubstring;value3-1-0=[no-nag]" -beta_version = getTemplateValue("https://wiki.mozilla.org/Template:BETA_VERSION") -aurora_version = getTemplateValue("https://wiki.mozilla.org/Template:AURORA_VERSION") -central_version = getTemplateValue("https://wiki.mozilla.org/Template:CENTRAL_VERSION") -esr_version = getTemplateValue("https://wiki.mozilla.org/Template:ESR_VERSION") -cycle_span = getTemplateValue("https://wiki.mozilla.org/Template:CURRENT_CYCLE") - -unlanded_beta_url = getReportURL("approval-mozilla-beta", cycle_span) + ";field0-0-0=cf_status_firefox" + beta_version + ";type0-0-0=nowordssubstr;value0-0-0=unaffected,fixed,verified,wontfix,disabled" + ";field0-1-0=cf_tracking_firefox" + beta_version + ";type0-1-0=equals;value0-1-0=%2B;field0-2-0=status_whiteboard;type0-2-0=notsubstring;value0-2-0=[no-nag]" -unlanded_aurora_url = getReportURL("approval-mozilla-aurora", cycle_span) + ";field0-0-0=cf_status_firefox" + aurora_version + ";type0-0-0=nowordssubstr;value0-0-0=unaffected,fixed,verified,wontfix,disabled" + ";field0-1-0=cf_tracking_firefox" + aurora_version + ";type0-1-0=equals;value0-1-0=%2B;field0-2-0=status_whiteboard;type0-2-0=notsubstring;value0-2-0=[no-nag]" -unlanded_esr38_url = getReportURL("approval-mozilla-esr38", cycle_span) + ";field0-0-0=cf_status_firefox_esr" + esr_version + ";type0-0-0=nowordssubstr;value0-0-0=unaffected,fixed,verified,wontfix,disabled" + ";field0-1-0=cf_tracking_firefox_esr" + esr_version + ";type0-1-0=equals;value0-1-0=%2B;field0-2-0=status_whiteboard;type0-2-0=notsubstring;value0-2-0=[no-nag]" - -tracking_beta_url = "https://bugzilla.mozilla.org/buglist.cgi?type1-0-0=equals;type0-1-0=notequals;type0-5-0=notequals;value0-5-0=disabled;value0-4-0=verified;field0-1-0=cf_status_firefox" + beta_version + ";field0-0-0=cf_tracking_firefox" + beta_version + ";field2-0-0=flagtypes.name;value0-3-0=unaffected;value0-6-0=verified%20disabled;value0-1-0=wontfix;field0-5-0=cf_status_firefox" + beta_version + ";type0-0-0=equals;value0-0-0=%2B;type0-2-0=notequals;negate1=1;field0-3-0=cf_status_firefox" + beta_version + ";type0-4-0=notequals;value2-0-0=approval-mozilla-beta%3F;field0-6-0=cf_status_firefox" + beta_version + ";type2-0-0=notsubstring;value0-2-0=fixed;type0-3-0=notequals;value1-0-0=core-security;field0-2-0=cf_status_firefox" + beta_version + ";field0-4-0=cf_status_firefox" + beta_version + ";type0-6-0=notequals;field1-0-0=bug_group;field2-1-0=status_whiteboard;type2-1-0=notsubstring;value2-1-0=[no-nag]" - -tracking_aurora_url = "https://bugzilla.mozilla.org/buglist.cgi?type1-0-0=equals;type0-1-0=notequals;type0-5-0=notequals;value0-5-0=disabled;value0-4-0=verified;type3-0-0=notequals;field0-1-0=cf_status_firefox" + aurora_version + ";field0-0-0=cf_tracking_firefox" + aurora_version + ";field2-0-0=flagtypes.name;value0-3-0=unaffected;value0-6-0=verified%20disabled;value0-1-0=wontfix;field0-5-0=cf_status_firefox" + aurora_version + ";type0-0-0=equals;value0-0-0=%2B;type0-2-0=notequals;negate1=1;field0-3-0=cf_status_firefox" + aurora_version + ";type0-4-0=notequals;value3-0-0=%2B;value2-0-0=approval-mozilla-aurora%3F;field0-6-0=cf_status_firefox" + aurora_version + ";field3-0-0=cf_tracking_firefox" + beta_version + ";type2-0-0=notsubstring;value0-2-0=fixed;type0-3-0=notequals;value1-0-0=core-security;field0-2-0=cf_status_firefox" + aurora_version + ";field0-4-0=cf_status_firefox" + aurora_version + ";type0-6-0=notequals;field1-0-0=bug_group;field2-1-0=status_whiteboard;type2-1-0=notsubstring;value2-1-0=[no-nag]" - -tracking_central_url = "https://bugzilla.mozilla.org/buglist.cgi?negate1=1;field0-3-0=cf_status_firefox" + central_version + ";value3-1-0=%2B;type1-0-0=equals;type0-1-0=notequals;type0-5-0=notequals;value0-5-0=disabled;value0-4-0=verified;type3-0-0=notequals;field0-1-0=cf_status_firefox" + central_version + ";field0-0-0=cf_tracking_firefox" + central_version + ";type0-4-0=notequals;value3-0-0=%2B;value2-0-0=approval-mozilla-aurora%3F;field2-0-0=flagtypes.name;field3-1-0=cf_tracking_firefox" + aurora_version + ";field0-6-0=cf_status_firefox" + central_version + ";value0-3-0=unaffected;field3-0-0=cf_tracking_firefox" + beta_version + ";type2-0-0=notsubstring;value0-2-0=fixed;value0-6-0=verified%20disabled;value0-1-0=wontfix;type0-3-0=notequals;value1-0-0=core-security;field0-2-0=cf_status_firefox" + central_version + ";type3-1-0=notequals;field0-5-0=cf_status_firefox" + central_version + ";field0-4-0=cf_status_firefox" + central_version + ";type0-6-0=notequals;type0-0-0=equals;value0-0-0=%2B;type0-2-0=notequals;field1-0-0=bug_group;field2-1-0=status_whiteboard;type2-1-0=notsubstring;value2-1-0=[no-nag]" - -tracking_beta_touch_url = "https://bugzilla.mozilla.org/buglist.cgi?negate1=1;field0-3-0=cf_status_firefox" + beta_version + ";type1-0-0=equals;type0-1-0=notequals;type0-5-0=notequals;value0-5-0=disabled;value0-4-0=verified;type3-0-0=greaterthan;field0-1-0=cf_status_firefox" + beta_version + ";field0-0-0=cf_tracking_firefox" + beta_version + ";type0-4-0=notequals;value3-0-0=3;value2-0-0=approval-mozilla-beta%3F;field2-0-0=flagtypes.name;field0-6-0=cf_status_firefox" + beta_version + ";value0-3-0=unaffected;field3-0-0=days_elapsed;type2-0-0=notsubstring;value0-2-0=fixed;value0-6-0=verified%20disabled;value0-1-0=wontfix;type0-3-0=notequals;value1-0-0=core-security;field0-2-0=cf_status_firefox" + beta_version + ";field0-5-0=cf_status_firefox" + beta_version + ";field0-4-0=cf_status_firefox" + beta_version + ";type0-6-0=notequals;type0-0-0=equals;value0-0-0=%2B;type0-2-0=notequals;field1-0-0=bug_group" + no_nag - -tracking_aurora_touch_url = "https://bugzilla.mozilla.org/buglist.cgi?negate1=1;field0-3-0=cf_status_firefox" + aurora_version + ";type4-0-0=greaterthan;type1-0-0=equals;type0-1-0=notequals;type0-5-0=notequals;value0-5-0=disabled;value0-4-0=verified;type3-0-0=notequals;field0-1-0=cf_status_firefox" + aurora_version + ";field0-0-0=cf_tracking_firefox" + aurora_version + ";type0-4-0=notequals;value3-0-0=%2B;field4-0-0=days_elapsed;value2-0-0=approval-mozilla-aurora%3F;field2-0-0=flagtypes.name;field0-6-0=cf_status_firefox" + aurora_version + ";value0-3-0=unaffected;field3-0-0=cf_tracking_firefox" + beta_version + ";type2-0-0=notsubstring;value0-2-0=fixed;value0-6-0=verified%20disabled;value0-1-0=wontfix;type0-3-0=notequals;value1-0-0=core-security;field0-2-0=cf_status_firefox" + aurora_version + ";field0-5-0=cf_status_firefox" + aurora_version + ";value4-0-0=3;field0-4-0=cf_status_firefox" + aurora_version + ";type0-6-0=notequals;type0-0-0=equals;value0-0-0=%2B;type0-2-0=notequals;field1-0-0=bug_group" + no_nag - -tracking_central_touch_url = "https://bugzilla.mozilla.org/buglist.cgi?negate1=1;field0-3-0=cf_status_firefox" + central_version + ";type1-0-0=equals;type2-1-0=notequals;type0-1-0=notequals;type0-5-0=notequals;value0-5-0=disabled;value0-4-0=verified;type3-0-0=greaterthan;field0-1-0=cf_status_firefox" + central_version + ";field0-0-0=cf_tracking_firefox" + central_version + ";type0-4-0=notequals;value3-0-0=3;value2-0-0=%2B;field2-0-0=cf_tracking_firefox" + beta_version + ";field0-6-0=cf_status_firefox" + central_version + ";value0-3-0=unaffected;field3-0-0=days_elapsed;type2-0-0=notequals;value0-2-0=fixed;value0-6-0=verified%" + central_version + "disabled;value0-1-0=wontfix;type0-3-0=notequals;value2-1-0=%2B;value1-0-0=core-security;field0-2-0=cf_status_firefox" + central_version + ";field0-5-0=cf_status_firefox" + central_version + ";field0-4-0=cf_status_firefox" + central_version + ";type0-6-0=notequals;type0-0-0=equals;value0-0-0=%2B;field2-1-0=cf_tracking_firefox" + aurora_version + ";type0-2-0=notequals;field1-0-0=bug_group" + no_nag - -tracking_esr38_url = "https://bugzilla.mozilla.org/buglist.cgi?type0-1-0=nowordssubstr;field0-1-0=cf_status_firefox_esr" + esr_version + ";field0-0-0=cf_tracking_firefox_esr" + esr_version + ";value0-1-0=fixed%20verified%20disabled%20wontfix%20unaffected;type0-0-0=equals;value0-0-0=" + beta_version + "%2B;field0-2-0=status_whiteboard;type0-2-0=notsubstring;value0-2-0=[no-nag]" - -needinfo_beta_url = "https://bugzilla.mozilla.org/buglist.cgi?f1=cf_tracking_firefox" + beta_version + "&v6=fixed%2Cwontfix%2Cunaffected%2Cverified%2Cdisabled&o1=anywords&resolution=---&o6=anywords&f12=flagtypes.name&v12=needinfo%3F&f11=OP&bug_status=UNCONFIRMED,NEW,READY,ASSIGNED,REOPENED&o12=equals&v1=%2B%2C%3F&f6=cf_status_firefox" + beta_version + "&n6=1" - -needinfo_aurora_url = "https://bugzilla.mozilla.org/buglist.cgi?f1=cf_tracking_firefox" + aurora_version + "&v6=fixed%2Cwontfix%2Cunaffected%2Cverified%2Cdisabled&o1=anywords&resolution=---&o6=anywords&f12=flagtypes.name&v12=needinfo%3F&f11=OP&bug_status=UNCONFIRMED,NEW,READY,ASSIGNED,REOPENED&o12=equals&v1=%2B%2C%3F&f6=cf_status_firefox" + aurora_version + "&n6=1" - -needinfo_central_url = "https://bugzilla.mozilla.org/buglist.cgi?f1=cf_tracking_firefox" + central_version + "&v6=fixed%2Cwontfix%2Cunaffected%2Cverified%2Cdisabled&o1=anywords&resolution=---&o6=anywords&f12=flagtypes.name&v12=needinfo%3F&f11=OP&bug_status=UNCONFIRMED,NEW,READY,ASSIGNED,REOPENED&o12=equals&v1=%2B%2C%3F&f6=cf_status_firefox" + central_version + "&n6=1" - -needinfo_esr38_url = "https://bugzilla.mozilla.org/buglist.cgi?f1=cf_tracking_firefox_esr" + esr_version + "&v6=fixed%2Cwontfix%2Cunaffected%2Cverified%2Cdisabled&o1=anywords&resolution=---&o6=anywords&f12=flagtypes.name&v12=needinfo%3F&f11=OP&bug_status=UNCONFIRMED,NEW,READY,ASSIGNED,REOPENED&o12=equals&v1=%2B%2C%3F&f6=cf_status_firefox_esr" + esr_version + "&n6=1" - -# TODO - sort the queries according to a priority flag -# TODO - separate queries for sec bugs, for now hide summary -# TODO - fix the 'untouched' queries - -# tracked beta/aurora/nightly nobodies: https://bugzilla.mozilla.org/buglist.cgi?j_top=OR&f1=cf_tracking_firefox25&o3=equals&v3=%2B&o1=equals&emailtype1=exact&o2=equals&emailassigned_to1=1&f3=cf_tracking_firefox27&f2=cf_tracking_firefox26&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&email1=nobody%40mozilla.org&v1=%2B&v2=%2B -# need same but for untouched - -urls = [ - (5, ["Unlanded Beta " + beta_version + " Bugs", "unlanded_beta", unlanded_beta_url, 0]), - (5, ["Unlanded Aurora " + aurora_version + " Bugs", "unlanded_aurora", unlanded_aurora_url, 0]), - (5, ["Unlanded ESR38 Bugs", "unlanded_esr38", unlanded_esr38_url, 0]), - (5, ["Tracked or Nominated for Tracking with Need-Info? Beta " + beta_version + " Bugs", "needinfo_beta", needinfo_beta_url, 0]), - (5, ["Tracked or Nominated for Tracking with Need-Info? Aurora " + aurora_version + " Bugs", "needinfo_aurora", needinfo_aurora_url, 0]), - (5, ["Tracked or Nominated for Tracking with Need-Info? Nightly " + central_version + " Bugs", "needinfo_central", needinfo_central_url, 0]), - (5, ["Tracked or Nominated for Tracking with Need-Info? ESR38 Bugs", "needinfo_esr38", needinfo_esr38_url, 0]), - (0, ["Bugs Tracked for Beta " + beta_version, "tracking_beta", tracking_beta_url, 0]), - (0, ["Bugs Tracked for Aurora " + aurora_version, "tracking_aurora", tracking_aurora_url, 0]), - (0, ["Bugs Tracked for Nightly " + central_version, "tracking_central", tracking_central_url, 0]), - (0, ["Bugs Tracked for ESR38", "tracking_esr38", tracking_esr38_url, 0]), - (3, ["Tracked Beta " + beta_version + " Bugs, untouched this week", "untouched_tracking_beta", tracking_beta_touch_url, 0]), - (3, ["Tracked Aurora " + aurora_version + " Bugs, untouched this week", "untouched_tracking_aurora", tracking_aurora_touch_url, 0]), - (3, ["Tracked Nightly " + central_version + " Bugs, untouched this week", "untouched_tracking_nightly", tracking_central_touch_url, 0]) -] - - -def createQuery(title, short_title, url, show_summary): - file_name = queries_dir + str(datetime.date.today()) + '_' + short_title - if not os.path.exists(queries_dir): - os.makedirs(queries_dir) - qf = open(file_name, 'w') - qf.write("query_name = \'" + title + "\'\n") - qf.write("query_url = \'" + url + "\'\n") - qf.write("show_summary = \'" + str(show_summary) + "\'\n") - return file_name - - -def createQueriesList(print_all): - queries = [] - weekday = datetime.datetime.today().weekday() - for url in urls: - if weekday >= 0 and weekday < 5 and url[0] == 5: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3])) - if weekday == 0 and url[0] == 0: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3])) - if weekday == 3 and url[0] == 3: - queries.append(createQuery(title=url[1][0], short_title=url[1][1], url=url[1][2], show_summary=url[1][3])) - print queries - return queries - - -def cleanUp(): - for file in os.listdir(queries_dir): - if file.startswith(str(datetime.date.today())): - os.remove(os.path.join(queries_dir, file)) - -if __name__ == '__main__': - parser = ArgumentParser(__doc__) - parser.set_defaults( - queries_only=False, - ) - parser.add_argument("-q", "--queries-only", dest="queries_only", action="store_true", - help="just create and print queries") - - options, args = parser.parse_known_args() - queries = createQueriesList(print_all=options.queries_only) - if options.queries_only: - for url in urls: - print url - else: - command = [ - scripts_dir + "email_nag.py", - "-t", "daily_email", - "--no-verification", - "-m", config['ldap_username'], - "-p", config['ldap_password'], - "-b", config['bz_api_key'], - "-e", "release-mgmt@mozilla.com"] - for query in queries: - command.append('-q') - command.append(query) - subject = datetime.datetime.today().strftime("%A %b %d") + " -- Daily Release Tracking Alert" - command.extend(['-s', subject]) - # send all other args to email_nag script argparser - command.extend(args) - subprocess.call(command) - cleanUp()