Esempio n. 1
0
def get_pkg_pushers(pkg, branch):
    watchers = []
    committers = []
    watchergroups = []
    committergroups = []

    if config.get('acl_system') == 'dummy':
        return ((['guest', 'admin'], ['guest', 'admin']),
                (['guest', 'admin'], ['guest', 'admin']))

    from pkgdb2client import PkgDB
    pkgdb = PkgDB(config.get('pkgdb_url'))
    acls = pkgdb.get_package(pkg, branches=branch)

    for package in acls['packages']:
        for acl in package.get('acls', []):
            if acl['status'] == 'Approved':
                if acl['acl'] == 'watchcommits':
                    name = acl['fas_name']
                    if name.startswith('group::'):
                        watchergroups.append(name.split('::')[1])
                    else:
                        watchers.append(name)
                elif acl['acl'] == 'commit':
                    name = acl['fas_name']
                    if name.startswith('group::'):
                        committergroups.append(name.split('::')[1])
                    else:
                        committers.append(name)

    return (committers, watchers), (committergroups, watchergroups)
Esempio n. 2
0
def get_critpath_components(collection='master', component_type='rpm'):
    """
    Return a list of critical path packages for a given collection.

    Args:
    collection (basestring): The collection/branch to search. Defaults to 'master'.
    component_type (basestring): The component type to search for. This only affects PDC queries.
        Defaults to 'rpm'.
    Returns:
        list: The critpath components for the given collection and type.
    """
    critpath_components = []
    critpath_type = config.get('critpath.type')
    if critpath_type != 'pdc' and component_type != 'rpm':
        log.warning('The critpath.type of "{0}" does not support searching for'
                    ' non-RPM components'.format(component_type))

    if critpath_type == 'pkgdb':
        from pkgdb2client import PkgDB
        pkgdb = PkgDB(config.get('pkgdb_url'))
        results = pkgdb.get_critpath_packages(branches=collection)
        if collection in results['pkgs']:
            critpath_components = results['pkgs'][collection]
    elif critpath_type == 'pdc':
        critpath_components = get_critpath_components_from_pdc(
            collection, component_type)
    else:
        critpath_components = config.get('critpath_pkgs')
    return critpath_components
Esempio n. 3
0
File: util.py Progetto: tyll/bodhi
def get_pkg_pushers(pkg, branch):
    watchers = []
    committers = []
    watchergroups = []
    committergroups = []

    if config.get('acl_system') == 'dummy':
        return ((['guest',
                  'admin'], ['guest',
                             'admin']), (['guest',
                                          'admin'], ['guest', 'admin']))

    from pkgdb2client import PkgDB
    pkgdb = PkgDB(config.get('pkgdb_url'))
    acls = pkgdb.get_package(pkg, branches=branch)

    for package in acls['packages']:
        for acl in package.get('acls', []):
            if acl['status'] == 'Approved':
                if acl['acl'] == 'watchcommits':
                    name = acl['fas_name']
                    if name.startswith('group::'):
                        watchergroups.append(name.split('::')[1])
                    else:
                        watchers.append(name)
                elif acl['acl'] == 'commit':
                    name = acl['fas_name']
                    if name.startswith('group::'):
                        committergroups.append(name.split('::')[1])
                    else:
                        committers.append(name)

    return (committers, watchers), (committergroups, watchergroups)
Esempio n. 4
0
    def __init__(self, irc):
        super(Fedora, self).__init__(irc)

        # caches, automatically downloaded on __init__, manually refreshed on
        # .refresh
        self.userlist = None
        self.bugzacl = None

        # To get the information, we need a username and password to FAS.
        # DO NOT COMMIT YOUR USERNAME AND PASSWORD TO THE PUBLIC REPOSITORY!
        self.fasurl = self.registryValue('fas.url')
        self.username = self.registryValue('fas.username')
        self.password = self.registryValue('fas.password')

        self.fasclient = AccountSystem(self.fasurl, username=self.username,
                                       password=self.password)
        self.pkgdb = PkgDB()
        # URLs
        # self.url = {}

        self.github_oauth_token = self.registryValue('github.oauth_token')

        self.karma_tokens = ('++', '--') if self.allow_negative else ('++',)

        # fetch necessary caches
        self._refresh()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)
Esempio n. 5
0
def get_critpath_pkgs(collection='master'):
    """Return a list of critical path packages for a given collection"""
    critpath_pkgs = []
    critpath_type = config.get('critpath.type')
    if critpath_type == 'pkgdb':
        from pkgdb2client import PkgDB
        pkgdb = PkgDB(config.get('pkgdb_url'))
        results = pkgdb.get_critpath_packages(branches=collection)
        if collection in results['pkgs']:
            critpath_pkgs = results['pkgs'][collection]
    else:
        critpath_pkgs = config.get('critpath_pkgs', '').split()
    return critpath_pkgs
Esempio n. 6
0
def get_critpath_pkgs(collection='master'):
    """Return a list of critical path packages for a given collection"""
    critpath_pkgs = []
    critpath_type = config.get('critpath.type')
    if critpath_type == 'pkgdb':
        from pkgdb2client import PkgDB
        pkgdb = PkgDB(config.get('pkgdb_url'))
        results = pkgdb.get_critpath_packages(branches=collection)
        if collection in results['pkgs']:
            critpath_pkgs = results['pkgs'][collection]
    else:
        critpath_pkgs = config.get('critpath_pkgs', '').split()
    return critpath_pkgs
Esempio n. 7
0
def get_critpath_pkgs(collection='master'):
    critpath_pkgs = []
    critpath_type = config.get('critpath.type', None)
    if critpath_type == 'pkgdb':
        from pkgdb2client import PkgDB
        pkgdb = PkgDB(config.get('pkgdb_url'))
        results = pkgdb.get_critpath_packages(branches=collection)
        if collection in results['pkgs']:
            critpath_pkgs = results['pkgs'][collection]
    else:
        # HACK: Avoid the current critpath policy for EPEL
        if not collection.startswith('EL'):
            # Note: ''.split() == []
            critpath_pkgs = config.get('critpath', '').split()
    return critpath_pkgs
Esempio n. 8
0
File: util.py Progetto: tyll/bodhi
def get_critpath_pkgs(collection='master'):
    critpath_pkgs = []
    critpath_type = config.get('critpath.type', None)
    if critpath_type == 'pkgdb':
        from pkgdb2client import PkgDB
        pkgdb = PkgDB(config.get('pkgdb_url'))
        results = pkgdb.get_critpath_packages(branches=collection)
        if collection in results['pkgs']:
            critpath_pkgs = results['pkgs'][collection]
    else:
        # HACK: Avoid the current critpath policy for EPEL
        if not collection.startswith('EL'):
            # Note: ''.split() == []
            critpath_pkgs = config.get('critpath', '').split()
    return critpath_pkgs
Esempio n. 9
0
    def __init__(self, irc):
        super(Fedora, self).__init__(irc)

        # caches, automatically downloaded on __init__, manually refreshed on
        # .refresh
        self.userlist = None
        self.bugzacl = None

        # To get the information, we need a username and password to FAS.
        # DO NOT COMMIT YOUR USERNAME AND PASSWORD TO THE PUBLIC REPOSITORY!
        self.fasurl = self.registryValue('fas.url')
        self.username = self.registryValue('fas.username')
        self.password = self.registryValue('fas.password')

        self.fasclient = AccountSystem(self.fasurl, username=self.username,
                                       password=self.password)
        self.pkgdb = PkgDB()
        # URLs
        # self.url = {}

        self.github_oauth_token = self.registryValue('github.oauth_token')

        # fetch necessary caches
        self._refresh()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)
Esempio n. 10
0
def get_critpath_components(collection='master',
                            component_type='rpm',
                            components=None):
    """
    Return a list of critical path packages for a given collection, filtered by components.

    Args:
        collection (basestring): The collection/branch to search. Defaults to 'master'.
        component_type (basestring): The component type to search for. This only affects PDC
            queries. Defaults to 'rpm'.
        components (frozenset or None): The list of components we are interested in. If None (the
            default), all components for the given collection and type are returned.
    Returns:
        list: The critpath components for the given collection and type.
    """
    critpath_components = []
    critpath_type = config.get('critpath.type')
    if critpath_type != 'pdc' and component_type != 'rpm':
        log.warning('The critpath.type of "{0}" does not support searching for'
                    ' non-RPM components'.format(component_type))

    if critpath_type == 'pkgdb':
        from pkgdb2client import PkgDB
        pkgdb = PkgDB(config.get('pkgdb_url'))
        results = pkgdb.get_critpath_packages(branches=collection)
        if collection in results['pkgs']:
            critpath_components = results['pkgs'][collection]
    elif critpath_type == 'pdc':
        critpath_components = get_critpath_components_from_pdc(
            collection, component_type, components)
    else:
        critpath_components = config.get('critpath_pkgs')

    # Filter the list of components down to what was requested, in case the specific path did
    # not take our request into account.
    if components is not None:
        critpath_components = [
            c for c in critpath_components if c in components
        ]

    return critpath_components
Esempio n. 11
0
    def __init__(self, bot):
        self.bot = bot

        fas_url = bot.config['fas']['url']
        fas_username = bot.config['fas']['username']
        fas_password = bot.config['fas']['password']
        self.fasclient = AccountSystem(fas_url,
                                       username=fas_username,
                                       password=fas_password)

        #self.log.info("Downloading package owners cache")
        data = requests.get(
            'https://admin.fedoraproject.org/pkgdb/api/bugzilla?format=json',
            verify=True).json()
        self.bugzacl = data['bugzillaAcls']

        self.pkgdb = PkgDB()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)
Esempio n. 12
0
    def __init__(self, bot):
        self.bot = bot

        fas_url = bot.config['fas']['url']
        fas_username = bot.config['fas']['username']
        fas_password = bot.config['fas']['password']
        self.fasclient = AccountSystem(
            fas_url, username=fas_username, password=fas_password)

        #self.log.info("Downloading package owners cache")
        data = requests.get(
            'https://admin.fedoraproject.org/pkgdb/api/bugzilla?format=json',
            verify=True).json()
        self.bugzacl = data['bugzillaAcls']

        self.pkgdb = PkgDB()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)
Esempio n. 13
0
def main():
    ''' Main function '''
    # Set up parser for global args
    parser = setup_parser()
    # Parse the commandline
    try:
        arg = parser.parse_args()
    except argparse.ArgumentTypeError as err:
        print("\nError: {0}".format(err))
        return 2

    logging.basicConfig()
    if arg.debug:
        LOG.setLevel(logging.DEBUG)
        PKGDBLOG.setLevel(logging.DEBUG)
    elif arg.verbose:
        LOG.setLevel(logging.INFO)

    global PKGDBCLIENT
    if arg.pkgdburl:
        LOG.info("Querying pkgdb at: %s", arg.pkgdburl)
        PKGDBCLIENT = PkgDB(arg.pkgdburl,
                            login_callback=pkgdb2client.ask_password)

    PKGDBCLIENT.insecure = arg.insecure

    if arg.bzurl:
        if not arg.bzurl.endswith('xmlrpc.cgi'):
            arg.bzurl = '%s/xmlrpc.cgi' % arg.bzurl
        LOG.info("Querying bugzilla at: %s", arg.pkgdburl)
        utils._get_bz(arg.bzurl, insecure=arg.insecure)

    if arg.fasurl:
        LOG.info("Querying FAS at: %s", arg.pkgdburl)
        utils.FASCLIENT.base_url = arg.fasurl
        utils.FASCLIENT.insecure = arg.insecure

    return_code = 0

    if arg.password:
        PKGDBCLIENT.password = arg.password
    if arg.username:
        PKGDBCLIENT.username = arg.username

    try:
        arg.func(arg)
    except KeyboardInterrupt:
        print("\nInterrupted by user.")
        return_code = 1
    except ServerError as err:
        LOG.debug('ServerError')
        print('{0}'.format(err))
        return_code = 3
    except ActionError as err:
        LOG.debug('ActionError')
        print('{0}'.format(err.message))
        return_code = 7
    except AppError as err:
        LOG.debug('AppError')
        print('{0}: {1}'.format(err.name, err.message))
        return_code = 4
    except PkgDBException as err:
        LOG.debug('PkgDBException')
        print('{0}'.format(err))
        return_code = 8
    except ValueError as err:
        print('Error: {0}'.format(err))
        print('Did you log in?')
        return_code = 6
    except Exception as err:
        print('Error: {0}'.format(err))
        logging.exception("Generic error catched:")
        return_code = 5

    return return_code
Esempio n. 14
0
def main():
    ''' Main function '''
    # Set up parser for global args
    parser = setup_parser()
    # Parse the commandline
    try:
        arg = parser.parse_args()
    except argparse.ArgumentTypeError as err:
        print("\nError: {0}".format(err))
        return 2

    if arg.nocolor:
        global RED, BOLD, RESET
        RED = ""
        BOLD = ""
        RESET = ""

    logging.basicConfig()
    if arg.debug:
        LOG.setLevel(logging.DEBUG)
        PKGDBLOG.setLevel(logging.DEBUG)
    elif arg.verbose:
        LOG.setLevel(logging.INFO)

    global pkgdbclient
    if arg.pkgdburl != pkgdb2client.PKGDB_URL:
        print("Querying pkgdb at: %s" % arg.pkgdburl)
        pkgdbclient = PkgDB(
            arg.pkgdburl,
            login_callback=pkgdb2client.ask_password)

    pkgdbclient.insecure = arg.insecure

    if arg.bzurl != pkgdb2client.BZ_URL:
        if not arg.bzurl.endswith('xmlrpc.cgi'):
            arg.bzurl = '%s/xmlrpc.cgi' % arg.bzurl
        print("Querying bugzilla at: %s" % arg.bzurl)
        utils._get_bz(arg.bzurl, insecure=arg.insecure)

    if arg.fasurl != pkgdb2client.FAS_URL:
        print("Querying FAS at: %s" % arg.fasurl)
        utils._get_fas(arg.fasurl, insecure=arg.insecure)

    if arg.kojihuburl != pkgdb2client.KOJI_HUB:
        print("Querying koji at: %s" % arg.kojihuburl)
        global KOJI_HUB
        KOJI_HUB = arg.kojihuburl

    return_code = 0

    if arg.password:
        pkgdbclient.password = arg.password
    if arg.username:
        pkgdbclient.username = arg.username

    try:
        arg.func(arg)
    except KeyboardInterrupt:
        print("\nInterrupted by user.")
        return_code = 1
    except ServerError as err:
        LOG.debug('ServerError')
        print('{0}'.format(err))
        return_code = 3
    except ActionError as err:
        LOG.debug('ActionError')
        print('{0}'.format(err.message))
        return_code = 7
    except argparse.ArgumentError as err:
        LOG.debug('ArgparseError')
        print('{0}'.format(err.message))
        return_code = 9
    except AppError as err:
        LOG.debug('AppError')
        print('{0}: {1}'.format(err.name, err.message))
        return_code = 4
    except PkgDBException as err:
        LOG.debug('PkgDBException')
        print('{0}'.format(err))
        return_code = 8
    except ValueError as err:
        print('Error: {0}'.format(err))
        print('Did you log in?')
        return_code = 6
    except Exception as err:
        print('Error: {0}'.format(err))
        logging.exception("Generic error catched:")
        return_code = 5

    return return_code
Esempio n. 15
0
from fedora.client import (AppError, ServerError)

import argparse
import requests
import logging
import koji
import itertools

from pkgdb2client import PkgDB, PkgDBException, __version__
import pkgdb2client
import pkgdb2client.utils


KOJI_HUB = 'http://koji.fedoraproject.org/kojihub'

pkgdbclient = PkgDB('https://admin.fedoraproject.org/pkgdb',
                    login_callback=pkgdb2client.ask_password)
BOLD = "\033[1m"
RED = "\033[0;31m"
RESET = "\033[0;0m"

# Initial simple logging stuff
logging.basicConfig()
PKGDBLOG = logging.getLogger("pkgdb2client")
LOG = logging.getLogger("pkgdb-cli")

ACTIONLIST = ['watchbugzilla', 'watchcommits', 'commit', 'approveacls']


class ActionError(Exception):
    ''' This class is raised when an ACL action is requested but not in
    the list of allowed action. '''
Esempio n. 16
0
def main():
    ''' Main function '''
    # Set up parser for global args
    parser = setup_parser()
    # Parse the commandline
    try:
        arg = parser.parse_args()
    except argparse.ArgumentTypeError as err:
        print("\nError: {0}".format(err))
        return 2

    logging.basicConfig()
    if arg.debug:
        LOG.setLevel(logging.DEBUG)
        PKGDBLOG.setLevel(logging.DEBUG)
    elif arg.verbose:
        LOG.setLevel(logging.INFO)

    global PKGDBCLIENT
    if arg.pkgdburl:
        LOG.info("Querying pkgdb at: %s", arg.pkgdburl)
        PKGDBCLIENT = PkgDB(
            arg.pkgdburl,
            login_callback=pkgdb2client.ask_password)

    PKGDBCLIENT.insecure = arg.insecure

    if arg.bzurl:
        if not arg.bzurl.endswith('xmlrpc.cgi'):
            arg.bzurl = '%s/xmlrpc.cgi' % arg.bzurl
        LOG.info("Querying bugzilla at: %s", arg.pkgdburl)
        utils._get_bz(arg.bzurl, insecure=arg.insecure)

    if arg.fasurl:
        LOG.info("Querying FAS at: %s", arg.pkgdburl)
        utils.FASCLIENT.base_url = arg.fasurl
        utils.FASCLIENT.insecure = arg.insecure

    return_code = 0

    if arg.password:
        PKGDBCLIENT.password = arg.password
    if arg.username:
        PKGDBCLIENT.username = arg.username

    try:
        arg.func(arg)
    except KeyboardInterrupt:
        print("\nInterrupted by user.")
        return_code = 1
    except ServerError as err:
        LOG.debug('ServerError')
        print('{0}'.format(err))
        return_code = 3
    except ActionError as err:
        LOG.debug('ActionError')
        print('{0}'.format(err.message))
        return_code = 7
    except AppError as err:
        LOG.debug('AppError')
        print('{0}: {1}'.format(err.name, err.message))
        return_code = 4
    except PkgDBException as err:
        LOG.debug('PkgDBException')
        print('{0}'.format(err))
        return_code = 8
    except ValueError as err:
        print('Error: {0}'.format(err))
        print('Did you log in?')
        return_code = 6
    except Exception as err:
        print('Error: {0}'.format(err))
        logging.exception("Generic error catched:")
        return_code = 5

    return return_code
Esempio n. 17
0
"""

from fedora.client import (AppError, ServerError)

import argparse
import requests
import logging
import koji
import itertools

from pkgdb2client import PkgDB, PkgDBException, __version__
import pkgdb2client
import pkgdb2client.utils as utils


pkgdbclient = PkgDB(
    pkgdb2client.PKGDB_URL, login_callback=pkgdb2client.ask_password)

BOLD = "\033[1m"
RED = "\033[0;31m"
RESET = "\033[0;0m"

# Initial simple logging stuff
logging.basicConfig()
PKGDBLOG = logging.getLogger("pkgdb2client")
LOG = logging.getLogger("pkgdb-cli")

ACTIONLIST = ['watchbugzilla', 'watchcommits', 'commit', 'approveacls']
KOJI_HUB = pkgdb2client.KOJI_HUB


class ActionError(Exception):
Esempio n. 18
0
class FedoraPlugin:
    """A plugin is a class which take the IrcBot as argument
    """

    requires = [
        'irc3.plugins.core',
        'irc3.plugins.command',
    ]

    def __init__(self, bot):
        self.bot = bot

        fas_url = bot.config['fas']['url']
        fas_username = bot.config['fas']['username']
        fas_password = bot.config['fas']['password']
        self.fasclient = AccountSystem(
            fas_url, username=fas_username, password=fas_password)

        #self.log.info("Downloading package owners cache")
        data = requests.get(
            'https://admin.fedoraproject.org/pkgdb/api/bugzilla?format=json',
            verify=True).json()
        self.bugzacl = data['bugzillaAcls']

        self.pkgdb = PkgDB()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)

    @staticmethod
    def _future_meetings(location):
        if not location.endswith('@irc.freenode.net'):
            location = '*****@*****.**' % location
        meetings = FedoraPlugin._query_fedocal(location=location)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            dt = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now < dt:
                yield dt, meeting

    @staticmethod
    def _meetings_for(calendar):
        meetings = FedoraPlugin._query_fedocal(calendar=calendar)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            start = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
            string = "%s %s" % (meeting['meeting_date_end'],
                                meeting['meeting_time_stop'])
            end = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now >= start and now <= end:
                yield meeting

    @staticmethod
    def _query_fedocal(**kwargs):
        url = 'https://apps.fedoraproject.org/calendar/api/meetings'
        return requests.get(url, params=kwargs).json()['meetings']

    @command
    def admins(self, mask, target, args):
        """admins <group name>

        Return the administrators list for the selected group

            %%admins <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    sponsors += person['username'] + ' '
            msg = 'Administrators for %s: %s' % (name, sponsors)
        except AppError:
            msg = 'There is no group %s.' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def badges(self, mask, target, args):
        """badges <username>

        Return badges statistics about a user.

            %%badges <username>
        """
        name = args['<username>']

        url = "https://badges.fedoraproject.org/user/" + name
        d = requests.get(url + "/json").json()

        if 'error' in d:
            response = d['error']
        else:
            template = "{name} has unlocked {n} Fedora Badges:  {url}"
            n = len(d['assertions'])
            response = template.format(name=name, url=url, n=n)

        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

    @command
    def branches(self, mask, target, args):
        """branches <package>

        Return the branches a package is in.

            %%branches <package>
        """
        package = args['<package>']

        try:
            pkginfo = self.pkgdb.get_package(package)
        except AppError:
            msg = "No such package exists."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        branch_list = []
        for listing in pkginfo['packages']:
            branch_list.append(listing['collection']['branchname'])
        branch_list.sort()
        msg = ' '.join(branch_list)
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def fas(self, mask, target, args):
        """fas <pattern>

        Searches a pattern in the list of FAS user

            %%fas <pattern>
        """
        users = self.fasclient.people_query(
                constraints={
                    #'username': args['<pattern>'],
                    'ircnick': args['<pattern>'],
                },
                columns=['username', 'ircnick', 'email']
            )
        if users:
            msg = ', '.join(
                [
                '%s (%s) <%s>' % (user.username, user.ircnick, user.email)
                for user in users
                ]
            )
        else:
            msg = 'No user matching found'
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def fasinfo(self, mask, target, args):
        """fasinfo <pattern>

        Return more information about the specified user

            %%fasinfo <username>
        """
        name = args['<username>']

        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Error getting info for user: "******"' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        if not person:
            msg = 'User "%s" doesn\'t exist' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        person['creation'] = person['creation'].split(' ')[0]
        string = (
            "User: %(username)s, Name: %(human_name)s"
            ", email: %(email)s, Creation: %(creation)s"
            ", IRC Nick: %(ircnick)s, Timezone: %(timezone)s"
            ", Locale: %(locale)s"
            ", GPG key ID: %(gpg_keyid)s, Status: %(status)s") % person
        self.bot.privmsg(target, '%s: %s' % (mask.nick, string))

        # List of unapproved groups is easy
        unapproved = ''
        for group in person['unapproved_memberships']:
            unapproved = unapproved + "%s " % group['name']
        if unapproved != '':
            msg = 'Unapproved Groups: %s' % unapproved
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

        # List of approved groups requires a separate query to extract roles
        constraints = {
            'username': name, 'group': '%',
            'role_status': 'approved'}
        columns = ['username', 'group', 'role_type']
        roles = []
        try:
            roles = self.fasclient.people_query(
                constraints=constraints,
                columns=columns)
        except:
            msg = 'Error getting group memberships.'
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        approved = ''
        for role in roles:
            if role['role_type'] == 'sponsor':
                approved += '+' + role['group'] + ' '
            elif role['role_type'] == 'administrator':
                approved += '@' + role['group'] + ' '
            else:
                approved += role['group'] + ' '
        if approved == '':
            approved = "None"
        msg = 'Approved Groups: %s' % approved
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def group(self, mask, target, args):
        """group <group short name>

        Return information about a Fedora Account System group.

            %%group <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_by_name(name)
            msg = '%s: %s' % (name, group['display_name'])
        except AppError:
            msg = 'There is no group "%s".' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def hellomynameis(self, mask, target, args):
        """hellomynameis <username>

        Return brief information about a Fedora Account System username. Useful
        for things like meeting roll call and calling attention to yourself.

            %%hellomynameis <username>
        """
        name = args['<username>']
        msg = None
        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Something blew up, please try again'
        if not person:
            msg = 'Sorry, but you don\'t exist'
        else:
            msg = '%(username)s \'%(human_name)s\' <%(email)s>' % person

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def himynameis(self, mask, target, args):
        """himynameis <username>

        Return information about a Fedora Account System group.

            %%himynameis <username>
        """
        name = args['<username>']
        msg = None
        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Something blew up, please try again'
        if not person:
            msg = 'Sorry, but you don\'t exist'
        else:
            msg = '%(username)s \'Slim Shady\' <%(email)s>' % person

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def localtime(self, mask, target, args):
        """localtime <username>

        Returns the current time of the user.
        The timezone is queried from FAS.

            %%localtime <username>
        """
        name = args['<username>']

        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Error getting info user user: "******"' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        if not person:
            msg = 'User "%s" doesn\'t exist' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        timezone_name = person['timezone']
        if timezone_name is None:
            msg = 'User "%s" doesn\'t share his timezone' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return
        try:
            time = datetime.datetime.now(pytz.timezone(timezone_name))
        except:
            msg = 'The timezone of "%s" was unknown: "%s"' % (
                name, timezone_name)
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        msg = 'The current local time of "%s" is: "%s" (timezone: %s)' % (
            name, time.strftime('%H:%M'), timezone_name)
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def members(self, mask, target, args):
        """members <group short name>

        Return the list of members for the selected group

            %%members <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_members(name)
            members = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    members += '@' + person['username'] + ' '
                elif person['role_type'] == 'sponsor':
                    members += '+' + person['username'] + ' '
                else:
                    members += person['username'] + ' '
            msg = 'Members of %s: %s' % (name, members)
        except AppError:
            msg = 'There is no group %s.' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def nextmeeting(self, mask, target, args):
        """nextmeeting <channel>

        Return the next meeting scheduled for a particular channel.

            %%nextmeeting <channel>
        """
        channel = args['<channel>']

        channel = channel.strip('#').split('@')[0]
        meetings = sorted(self._future_meetings(channel), key=itemgetter(0))

        test, meetings = tee(meetings)
        try:
            test.next()
        except StopIteration:
            response = "There are no meetings scheduled for #%s." % channel
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            return

        for date, meeting in islice(meetings, 0, 3):
            response = "In #%s is %s (starting %s)" % (
                channel,
                meeting['meeting_name'],
                arrow.get(date).humanize(),
            )
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
        base = "https://apps.fedoraproject.org/calendar/location/"
        url = base + urllib.quote("%[email protected]/" % channel)
        self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))

    @command
    def nextmeetings(self, mask, target, args):
        """nextmeetings

        Return the next meetings scheduled for any channel(s).

            %%nextmeetings
        """
        msg = 'One moment, please...  Looking up the channel list.'
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

        url = 'https://apps.fedoraproject.org/calendar/api/locations/'
        locations = requests.get(url).json()['locations']
        meetings = sorted(chain(*[
            self._future_meetings(location)
            for location in locations
            if 'irc.freenode.net' in location
        ]), key=itemgetter(0))

        test, meetings = tee(meetings)
        try:
            test.next()
        except StopIteration:
            response = "There are no meetings scheduled at all."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            return

        for date, meeting in islice(meetings, 0, 5):
            response = "In #%s is %s (starting %s)" % (
                meeting['meeting_location'].split('@')[0].strip(),
                meeting['meeting_name'],
                arrow.get(date).humanize(),
            )
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

    @command
    def pushduty(self, mask, target, args):
        """pushduty

        Return the list of people who are on releng push duty right now.

            %%pushduty
        """

        def get_persons():
            for meeting in self._meetings_for('release-engineering'):
                yield meeting['meeting_name']

        persons = list(get_persons())

        url = "https://apps.fedoraproject.org/" + \
            "calendar/release-engineering/"

        if not persons:
            response = "Nobody is listed as being on push duty right now..."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))
            return

        persons = ", ".join(persons)
        response = "The following people are on push duty: %s" % persons
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
        self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))

    @command
    def quote(self, mask, target, args):
        """quote <SYMBOL> [daily, weekly, monthly, quarterly]

        Return some datagrepper statistics on fedmsg categories.

            %%quote <symbol> <frame>
        """

        symbol = args['<symbol>']
        frame = 'daily'
        if '<frame>' in args:
            frame = args['<frame>']

        # Second, build a lookup table for symbols.  By default, we'll use the
        # fedmsg category names, take their first 3 characters and uppercase
        # them.  That will take things like "wiki" and turn them into "WIK" and
        # "bodhi" and turn them into "BOD".  This handles a lot for us.  We'll
        # then override those that don't make sense manually here.  For
        # instance "fedoratagger" by default would be "FED", but that's no
        # good.  We want "TAG".
        # Why all this trouble?  Well, as new things get added to the fedmsg
        # bus, we don't want to have keep coming back here and modifying this
        # code.  Hopefully this dance will at least partially future-proof us.
        symbols = dict([
            (processor.__name__.lower(), processor.__name__[:3].upper())
            for processor in fedmsg.meta.processors
        ])
        symbols.update({
            'fedoratagger': 'TAG',
            'fedbadges': 'BDG',
            'buildsys': 'KOJ',
            'pkgdb': 'PKG',
            'meetbot': 'MTB',
            'planet': 'PLN',
            'trac': 'TRC',
            'mailman': 'MM3',
        })

        # Now invert the dict so we can lookup the argued symbol.
        # Yes, this is vulnerable to collisions.
        symbols = dict([(sym, name) for name, sym in symbols.items()])

        # These aren't user-facing topics, so drop 'em.
        del symbols['LOG']
        del symbols['UNH']
        del symbols['ANN']  # And this one is unused...

        key_fmt = lambda d: ', '.join(sorted(d.keys()))

        if symbol not in symbols:
            response = "No such symbol %r.  Try one of %s"
            msg = response % (symbol, key_fmt(symbols))
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        # Now, build another lookup of our various timeframes.
        frames = dict(
            daily=datetime.timedelta(days=1),
            weekly=datetime.timedelta(days=7),
            monthly=datetime.timedelta(days=30),
            quarterly=datetime.timedelta(days=91),
        )

        if frame not in frames:
            response = "No such timeframe %r.  Try one of %s"
            msg = response % (frame, key_fmt(frames))
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        category = [symbols[symbol]]

        t2 = datetime.datetime.utcnow()
        t1 = t2 - frames[frame]
        t0 = t1 - frames[frame]

        # Count the number of messages between t0 and t1, and between t1 and t2
        query1 = dict(start=t0, end=t1, category=category)
        query2 = dict(start=t1, end=t2, category=category)

        # Do this async for superfast datagrepper queries.
        tpool = ThreadPool()
        batched_values = tpool.map(datagrepper_query, [
            dict(start=x, end=y, category=category)
            for x, y in Utils.daterange(t1, t2, SPARKLINE_RESOLUTION)
        ] + [query1, query2])

        count2 = batched_values.pop()
        count1 = batched_values.pop()

        # Just rename the results.  We'll use the rest for the sparkline.
        sparkline_values = batched_values

        yester_phrases = dict(
            daily="yesterday",
            weekly="the week preceding this one",
            monthly="the month preceding this one",
            quarterly="the 3 months preceding these past three months",
        )
        phrases = dict(
            daily="24 hours",
            weekly="week",
            monthly="month",
            quarterly="3 months",
        )

        if count1 and count2:
            percent = ((float(count2) / count1) - 1) * 100
        elif not count1 and count2:
            # If the older of the two time periods had zero messages, but there
            # are some in the more current period.. well, that's an infinite
            # percent increase.
            percent = float('inf')
        elif not count1 and not count2:
            # If counts are zero for both periods, then the change is 0%.
            percent = 0
        else:
            # Else, if there were some messages in the old time period, but
            # none in the current... then that's a 100% drop off.
            percent = -100

        sign = lambda value: value >= 0 and '+' or '-'

        template = u"{sym}, {name} {sign}{percent:.2f}% over {phrase}"
        response = template.format(
            sym=symbol,
            name=symbols[symbol],
            sign=sign(percent),
            percent=abs(percent),
            phrase=yester_phrases[frame],
        )
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

        # Now, make a graph out of it.
        sparkline = Utils.sparkline(sparkline_values)

        template = u"     {sparkline}  ⤆ over {phrase}"
        response = template.format(
            sym=symbol,
            sparkline=sparkline,
            phrase=phrases[frame]
        )
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

        to_utc = lambda t: time.gmtime(time.mktime(t.timetuple()))
        # And a final line for "x-axis tics"
        t1_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t1))
        t2_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t2))
        padding = u" " * (SPARKLINE_RESOLUTION - len(t1_fmt) - 3)
        template = u"     ↑ {t1}{padding}↑ {t2}"
        response = template.format(t1=t1_fmt, t2=t2_fmt, padding=padding)
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

    @command
    def sponsors(self, mask, target, args):
        """sponsors <group short name>

        Return the sponsors list for the selected group

            %%sponsors <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'sponsor':
                    sponsors += person['username'] + ' '
                elif person['role_type'] == 'administrator':
                    sponsors += '@' + person['username'] + ' '
            msg = 'Sponsors for %s: %s' % (name, sponsors)
        except AppError:
            msg = 'There is no group %s.' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def vacation(self, mask, target, args):
        """vacation

        Return the list of people who are on vacation right now according
        to fedocal.

            %%vacation
        """

        def get_persons():
            for meeting in self._meetings_for('vacation'):
                for manager in meeting['meeting_manager']:
                    yield manager

        persons = list(get_persons())

        if not persons:
            response = "Nobody is listed as being on vacation right now..."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            url = "https://apps.fedoraproject.org/calendar/vacation/"
            self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))
            return

        persons = ", ".join(persons)
        response = "The following people are on vacation: %s" % persons
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
        url = "https://apps.fedoraproject.org/calendar/vacation/"
        self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))

    @command
    def what(self, mask, target, args):
        """what <package>

        Returns a description of a given package.

            %%what <package>
        """
        package = args['<package>']
        msg = None
        try:
            summary = self.bugzacl['Fedora'][package]['summary']
            msg = "%s: %s" % (package, summary)
        except KeyError:
            msg = "No such package exists."

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def whoowns(self, mask, target, args):
        """whoowns <package>

        Return more information about the specified user

            %%whoowns <package>
        """

        package = args['<package>']

        try:
            mainowner = self.bugzacl['Fedora'][package]['owner']
        except KeyError:
            msg = "No such package exists."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        others = []
        for key in self.bugzacl:
            if key == 'Fedora':
                continue
            try:
                owner = self.bugzacl[key][package]['owner']
                if owner == mainowner:
                    continue
            except KeyError:
                continue
            others.append("%s in %s" % (owner, key))

        if others == []:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, mainowner))
        else:
            msg = "%s (%s)" % (mainowner, ', '.join(others))
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def wikilink(self, mask, target, args):
        """wikilink <username>

        Return MediaWiki link syntax for a FAS user's page on the wiki.

            %%wikilink <username>
        """
        name = args['<username>']

        person = msg = None
        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Error getting info for user: "******"' % name
        if not person:
            msg = 'User "%s" doesn\'t exist' % name
        else:
            msg = "[[User:%s|%s]]" % (person["username"],
                                      person["human_name"] or '')

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
Esempio n. 19
0
class TestPkgdDBAuth(unittest.TestCase):
    ''' Authenticated pkgdb2 tests. '''
    @auth_only
    def setUp(self):
        """ set up data used in the tests.
        setUp is called before each test function execution.
        """
        self.pkgdb = PkgDB(PKGDB_URL, insecure=True)
        self.pkgdb.login(USERNAME, PASSWORD)

    @auth_only
    def test_01_create_collection(self):
        ''' Test the create_collection function. '''
        out = self.pkgdb.create_collection(
            clt_name='Test',
            version=VERSION,
            clt_status='Active',
            branchname=COL_NAME,
            dist_tag='.tst' + COL_NAME[:20],
            git_branch_name='test',
            kojiname='test',
        )

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'],
                         ['Collection "%s" created' % COL_NAME])

        self.assertRaises(
            PkgDBException,
            self.pkgdb.create_collection,
            clt_name='Test',
            version=VERSION,
            clt_status='Active',
            branchname=COL_NAME,
            dist_tag='.tst' + COL_NAME[:20],
            git_branch_name='test',
            kojiname='test',
        )

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'],
                         ['Collection "%s" created' % COL_NAME])

    @auth_only
    def test_02_create_package(self):
        ''' Test the create_package function. '''

        out = self.pkgdb.create_package(pkgname=PKG_NAME,
                                        summary='Test package',
                                        description='Test package desc',
                                        review_url='https://bz.com',
                                        status='Approved',
                                        shouldopen=False,
                                        branches=COL_NAME,
                                        poc='pingou',
                                        upstream_url='http://guake.org',
                                        critpath=False)

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], ['Package created'])

    @auth_only
    def test_03_orphan_packages(self):
        ''' Test the orphan_packages function. '''

        out = self.pkgdb.orphan_packages('guake', ['master', 'el6'])

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'user: pingou changed point of contact of package: guake from: '
            'pingou to: orphan on branch: master',
            'user: pingou changed point of contact of package: guake from: '
            'pingou to: orphan on branch: el6'
        ])

    @auth_only
    def test_04_unorphan_packages(self):
        ''' Test the unorphan_packages function. '''

        out = self.pkgdb.unorphan_packages('guake', ['master', 'el6'],
                                           'pingou')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'Package guake has been unorphaned on master by pingou',
            'Package guake has been unorphaned on el6 by pingou'
        ])

    @auth_only
    def test_05_retire_packages(self):
        ''' Test the retire_packages function. '''

        out = self.pkgdb.retire_packages('guake', 'master')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'user: pingou updated package: guake status from: Approved to '
            'Retired on branch: master'
        ])

    @auth_only
    def test_06_unretire_packages(self):
        ''' Test the unretire_packages function. '''

        out = self.pkgdb.unretire_packages('guake', 'master')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'user: pingou updated package: guake status from: Retired to '
            'Approved on branch: master'
        ])

    @auth_only
    def test_07_update_acl(self):
        ''' Test the update_acl function. '''

        # After un-retiring the package on master, we need to re-set Ralph's
        # pending ACL request
        out = self.pkgdb.update_acl('guake', ['master', 'el6'], 'commit',
                                    'Awaiting Review', 'ralph')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'user: pingou set for ralph acl: commit of package: guake from: '
            'Obsolete to: Awaiting Review on branch: master',
            'Nothing to update on branch: el6 for acl: commit'
        ])

        # Check the output when we try to change an ACL to what it is already
        out = self.pkgdb.update_acl('guake', ['master', 'el6'], 'commit',
                                    'Awaiting Review', 'ralph')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'Nothing to update on branch: master for acl: commit',
            'Nothing to update on branch: el6 for acl: commit'
        ])

    @auth_only
    def test_08_update_collection_status(self):
        ''' Test the update_collection_status function. '''

        out = self.pkgdb.update_collection_status(COL_NAME, 'EOL')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'],
                         ['Collection updated from "Active" to "EOL"'])

    @auth_only
    def test_09_update_package_poc(self):
        ''' Test the update_package_poc function. '''

        out = self.pkgdb.update_package_poc('guake', ['master', 'el6'],
                                            'ralph')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'user: pingou changed point of contact of package: guake from: '
            'orphan to: ralph on branch: master',
            'user: pingou changed point of contact of package: guake from: '
            'pingou to: ralph on branch: el6'
        ])

        out = self.pkgdb.update_package_poc('guake', ['master', 'el6'],
                                            'pingou')

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'user: pingou changed point of contact of package: guake from: '
            'ralph to: pingou on branch: master',
            'user: pingou changed point of contact of package: guake from: '
            'ralph to: pingou on branch: el6'
        ])

    @auth_only
    def test_10_update_critpath(self):
        ''' Test the update_critpath function. '''

        # Check before changing the critpath
        out = self.pkgdb.get_package('guake')
        self.assertEqual(sorted(out.keys()), ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        critpaths = [el['critpath'] for el in out['packages']]
        branches = [el['collection']['branchname'] for el in out['packages']]
        self.assertEqual(critpaths, [False, False, False, False, False])
        self.assertEqual(branches, ['master', 'el6', 'f19', 'f20', 'f21'])

        out = self.pkgdb.update_critpath('guake', ['master', 'el6'], True)

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'guake: critpath updated on master to True',
            'guake: critpath updated on el6 to True'
        ])

        # Check after changing the critpath
        out = self.pkgdb.get_package('guake')
        self.assertEqual(sorted(out.keys()), ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        critpaths = [el['critpath'] for el in out['packages']]
        branches = [el['collection']['branchname'] for el in out['packages']]
        self.assertEqual(critpaths, [True, True, False, False, False])
        self.assertEqual(branches, ['master', 'el6', 'f19', 'f20', 'f21'])

        out = self.pkgdb.get_package('guake')
        self.assertEqual(sorted(out.keys()), ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        self.assertEqual(out['packages'][0]['collection']['branchname'],
                         'master')
        self.assertEqual(out['packages'][0]['package']['name'], 'guake')
        self.assertEqual(out['packages'][0]['point_of_contact'], 'pingou')

        out = self.pkgdb.update_critpath('guake', ['master', 'el6'], False)

        self.assertEqual(sorted(out.keys()), ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(out['messages'], [
            'guake: critpath updated on master to False',
            'guake: critpath updated on el6 to False'
        ])

        # Check after reste critpath to False
        out = self.pkgdb.get_package('guake')
        self.assertEqual(sorted(out.keys()), ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        critpaths = [el['critpath'] for el in out['packages']]
        branches = [el['collection']['branchname'] for el in out['packages']]
        self.assertEqual(critpaths, [False, False, False, False, False])
        self.assertEqual(branches, ['master', 'el6', 'f19', 'f20', 'f21'])
Esempio n. 20
0
class TestPkgdDB(unittest.TestCase):
    ''' Un-authenticated pkgdb2 tests. '''

    def setUp(self):
        """ set up data used in the tests.
        setUp is called before each test function execution.
        """
        self.pkgdb = PkgDB(PKGDB_URL, insecure=True)

    def test_get_collection(self):
        ''' Test the get_collections function. '''
        out = self.pkgdb.get_collections()
        self.assertEqual(
            sorted(out.keys()),
            ['collections', 'output'])
        self.assertTrue(len(out['collections']) >= 30)

        out = self.pkgdb.get_collections(pattern='f19')
        self.assertEqual(len(out['collections']), 1)
        self.assertEqual(out['collections'][0]['branchname'], 'f19')
        self.assertEqual(
            sorted(out.keys()),
            ['collections', 'output'])

        out = self.pkgdb.get_collections(
            clt_status=['EOL', 'Under Development'])
        self.assertEqual(
            sorted(out.keys()),
            ['collections', 'output'])
        self.assertTrue(len(out['collections']) >= 25)

    def test_get_package(self):
        ''' Test the get_package function. '''
        out = self.pkgdb.get_package('guake')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        self.assertEqual(
            out['packages'][0]['collection']['branchname'], 'master')
        self.assertEqual(
            out['packages'][0]['package']['name'], 'guake')
        self.assertEqual(
            out['packages'][0]['point_of_contact'], 'pingou')

        out = self.pkgdb.get_package('guake', 'f20')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 1)
        self.assertEqual(
            out['packages'][0]['collection']['branchname'], 'f20')
        self.assertEqual(
            out['packages'][0]['package']['name'], 'guake')
        out = self.pkgdb.get_package('guake', ['f20', 'f19'])
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 2)
        self.assertEqual(
            out['packages'][0]['collection']['branchname'], 'f19')
        self.assertEqual(
            out['packages'][1]['collection']['branchname'], 'f20')
        self.assertEqual(
            out['packages'][0]['package']['name'], 'guake')
        self.assertEqual(
            out['packages'][1]['package']['name'], 'guake')

    def test_get_packager_acls(self):
        ''' Test the get_packager_acls function. '''
        out = self.pkgdb.get_packager_acls('pingou')
        self.assertEqual(
            sorted(out.keys()),
            ['acls', 'output', 'page', 'page_total'])
        self.assertEqual(len(out['acls']), 250)
        self.assertEqual(out['page_total'], 6)

        out = self.pkgdb.get_packager_acls('pingou', acls=['approveacls'])
        self.assertEqual(
            sorted(out.keys()),
            ['acls', 'output', 'page', 'page_total'])
        self.assertTrue(len(out['acls']) >= 239)
        self.assertEqual(out['page_total'], 2)

        out = self.pkgdb.get_packager_acls('pingou', page=3)
        self.assertEqual(
            sorted(out.keys()),
            ['acls', 'output', 'page', 'page_total'])
        self.assertTrue(len(out['acls']) >= 250)
        self.assertEqual(out['page_total'], 6)

        out = self.pkgdb.get_packager_acls('pingou', count=True)
        self.assertEqual(
            sorted(out.keys()),
            ['acls_count', 'output', 'page', 'page_total'])
        self.assertTrue(out['acls_count'] >= 1043)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packager_acls('pingou', poc=True, count=True)
        self.assertEqual(
            sorted(out.keys()),
            ['acls_count', 'output', 'page', 'page_total'])
        self.assertTrue(out['acls_count'] >= 750)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packager_acls('pingou', poc=False, count=True)
        self.assertEqual(
            sorted(out.keys()),
            ['acls_count', 'output', 'page', 'page_total'])
        self.assertTrue(out['acls_count'] >= 239)
        self.assertEqual(out['page_total'], 1)

    def test_get_packager_stats(self):
        ''' Test the get_packager_stats function. '''
        out = self.pkgdb.get_packager_stats('pingou')
        self.assertEqual(
            sorted(out.keys()),
            ['el5', 'el6', 'epel7', 'f19', 'f20', 'f21', 'master', 'output'])
        self.assertEqual(
            sorted(out['master'].keys()),
            ['co-maintainer', 'point of contact'])
        self.assertTrue(out['master']['point of contact'] >= 50)

    def test_get_packagers(self):
        ''' Test the get_packagers function. '''
        out = self.pkgdb.get_packagers('ping*')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packagers'])
        self.assertEqual(out['packagers'], ['pingou'])

    def test_get_packages(self):
        ''' Test the get_packages function. '''
        out = self.pkgdb.get_packages('gua*')

        expected_keys = [
            'acls', 'creation_date', 'description', 'monitor', 'name',
            'review_url', 'status', 'summary', 'upstream_url',
        ]

        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 10)
        self.assertEqual(out['packages'][0]['name'], 'guacamole-client')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['packages'][1]['name'], 'guacamole-common')
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', branches='el6')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 7)
        self.assertEqual(out['packages'][0]['name'], 'guacamole-common')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['packages'][1]['name'], 'guacamole-ext')
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', poc='pingou')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 1)
        self.assertEqual(out['packages'][0]['name'], 'guake')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', status='Retired')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 5)
        self.assertEqual(out['packages'][0]['name'], 'guacd')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('g*', orphaned=True)
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertTrue(len(out['packages']) >= 44)
        self.assertEqual(out['packages'][0]['name'], 'ghex')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        #self.assertEqual(out['packages'][1]['name'], 'glom')
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', poc='pingou', acls=True)
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 1)
        self.assertEqual(out['packages'][0]['name'], 'guake')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('g*', page=2)
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 250)
        self.assertEqual(out['packages'][0]['name'], 'ghc-parameterized-data')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 2)
        self.assertEqual(out['page_total'], 6)

        out = self.pkgdb.get_packages('g*', count=True)
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages', 'page', 'page_total'])
        self.assertTrue(out['packages'] >= 1340)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)
Esempio n. 21
0
 def setUp(self):
     """ set up data used in the tests.
     setUp is called before each test function execution.
     """
     self.pkgdb = PkgDB(PKGDB_URL, insecure=True)
Esempio n. 22
0
# license.
"""

from fedora.client import (AppError, ServerError)

import argparse
import logging

from six.moves import input

from pkgdb2client import PkgDB, PkgDBException, __version__
from pkgdb2client.cli import ActionError
import pkgdb2client
import pkgdb2client.utils as utils

PKGDBCLIENT = PkgDB('https://admin.fedoraproject.org/pkgdb',
                    login_callback=pkgdb2client.ask_password)
BOLD = "\033[1m"
RED = "\033[0;31m"
RESET = "\033[0;0m"

# Initial simple logging stuff
logging.basicConfig()
PKGDBLOG = logging.getLogger("pkgdb2client")
LOG = logging.getLogger("pkgdb-admin")


def setup_parser():
    '''
    Set the main arguments.
    '''
    parser = argparse.ArgumentParser(prog="pkgdb-admin")
Esempio n. 23
0
class TestPkgdDBAuth(unittest.TestCase):
    ''' Authenticated pkgdb2 tests. '''

    @auth_only
    def setUp(self):
        """ set up data used in the tests.
        setUp is called before each test function execution.
        """
        self.pkgdb = PkgDB(PKGDB_URL, insecure=True)
        self.pkgdb.login(USERNAME, PASSWORD)

    @auth_only
    def test_01_create_collection(self):
        ''' Test the create_collection function. '''
        out = self.pkgdb.create_collection(
            clt_name='Test',
            version=VERSION,
            clt_status='Active',
            branchname=COL_NAME,
            dist_tag='.tst' + COL_NAME[:20],
            git_branch_name='test',
            kojiname='test',
        )

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['Collection "%s" created' % COL_NAME])

        self.assertRaises(
            PkgDBException,
            self.pkgdb.create_collection,
            clt_name='Test',
            version=VERSION,
            clt_status='Active',
            branchname=COL_NAME,
            dist_tag='.tst' + COL_NAME[:20],
            git_branch_name='test',
            kojiname='test',
        )

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['Collection "%s" created' % COL_NAME])

    @auth_only
    def test_02_create_package(self):
        ''' Test the create_package function. '''

        out = self.pkgdb.create_package(
            pkgname=PKG_NAME,
            summary='Test package',
            description='Test package desc',
            review_url='https://bz.com',
            status='Approved',
            shouldopen=False,
            branches=COL_NAME,
            poc='pingou',
            upstream_url='http://guake.org',
            critpath=False)

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['Package created'])

    @auth_only
    def test_03_orphan_packages(self):
        ''' Test the orphan_packages function. '''

        out = self.pkgdb.orphan_packages('guake', ['master', 'el6'])

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['user: pingou changed point of contact of package: guake from: '
             'pingou to: orphan on branch: master',
             'user: pingou changed point of contact of package: guake from: '
             'pingou to: orphan on branch: el6'])

    @auth_only
    def test_04_unorphan_packages(self):
        ''' Test the unorphan_packages function. '''

        out = self.pkgdb.unorphan_packages(
            'guake', ['master', 'el6'], 'pingou')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['Package guake has been unorphaned on master by pingou',
             'Package guake has been unorphaned on el6 by pingou'])

    @auth_only
    def test_05_retire_packages(self):
        ''' Test the retire_packages function. '''

        out = self.pkgdb.retire_packages('guake', 'master')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['user: pingou updated package: guake status from: Approved to '
             'Retired on branch: master'])

    @auth_only
    def test_06_unretire_packages(self):
        ''' Test the unretire_packages function. '''

        out = self.pkgdb.unretire_packages('guake', 'master')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['user: pingou updated package: guake status from: Retired to '
             'Approved on branch: master'])

    @auth_only
    def test_07_update_acl(self):
        ''' Test the update_acl function. '''

        # After un-retiring the package on master, we need to re-set Ralph's
        # pending ACL request
        out = self.pkgdb.update_acl(
            'guake', ['master', 'el6'], 'commit', 'Awaiting Review',
            'ralph')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['user: pingou set for ralph acl: commit of package: guake from: '
             'Obsolete to: Awaiting Review on branch: master',
             'Nothing to update on branch: el6 for acl: commit'])

        # Check the output when we try to change an ACL to what it is already
        out = self.pkgdb.update_acl(
            'guake', ['master', 'el6'], 'commit', 'Awaiting Review',
            'ralph')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['Nothing to update on branch: master for acl: commit',
             'Nothing to update on branch: el6 for acl: commit'])

    @auth_only
    def test_08_update_collection_status(self):
        ''' Test the update_collection_status function. '''

        out = self.pkgdb.update_collection_status(COL_NAME, 'EOL')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['Collection updated from "Active" to "EOL"'])

    @auth_only
    def test_09_update_package_poc(self):
        ''' Test the update_package_poc function. '''

        out = self.pkgdb.update_package_poc(
            'guake', ['master', 'el6'], 'ralph')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['user: pingou changed point of contact of package: guake from: '
             'orphan to: ralph on branch: master',
             'user: pingou changed point of contact of package: guake from: '
             'pingou to: ralph on branch: el6'])

        out = self.pkgdb.update_package_poc(
            'guake', ['master', 'el6'], 'pingou')

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['user: pingou changed point of contact of package: guake from: '
             'ralph to: pingou on branch: master',
             'user: pingou changed point of contact of package: guake from: '
             'ralph to: pingou on branch: el6'])

    @auth_only
    def test_10_update_critpath(self):
        ''' Test the update_critpath function. '''

        # Check before changing the critpath
        out = self.pkgdb.get_package('guake')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        critpaths = [el['critpath'] for el in out['packages']]
        branches = [el['collection']['branchname'] for el in out['packages']]
        self.assertEqual(
            critpaths,
            [False, False, False, False, False]
        )
        self.assertEqual(
            branches,
            ['master', 'el6', 'f19', 'f20', 'f21']
        )

        out = self.pkgdb.update_critpath(
            'guake', ['master', 'el6'], True)

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['guake: critpath updated on master to True',
             'guake: critpath updated on el6 to True']
        )

        # Check after changing the critpath
        out = self.pkgdb.get_package('guake')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        critpaths = [el['critpath'] for el in out['packages']]
        branches = [el['collection']['branchname'] for el in out['packages']]
        self.assertEqual(
            critpaths,
            [True, True, False, False, False]
        )
        self.assertEqual(
            branches,
            ['master', 'el6', 'f19', 'f20', 'f21']
        )

        out = self.pkgdb.get_package('guake')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        self.assertEqual(
            out['packages'][0]['collection']['branchname'], 'master')
        self.assertEqual(
            out['packages'][0]['package']['name'], 'guake')
        self.assertEqual(
            out['packages'][0]['point_of_contact'], 'pingou')

        out = self.pkgdb.update_critpath(
            'guake', ['master', 'el6'], False)

        self.assertEqual(
            sorted(out.keys()),
            ['messages', 'output'])

        self.assertEqual(out['output'], 'ok')
        self.assertEqual(
            out['messages'],
            ['guake: critpath updated on master to False',
             'guake: critpath updated on el6 to False']
        )

        # Check after reste critpath to False
        out = self.pkgdb.get_package('guake')
        self.assertEqual(
            sorted(out.keys()),
            ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        critpaths = [el['critpath'] for el in out['packages']]
        branches = [el['collection']['branchname'] for el in out['packages']]
        self.assertEqual(
            critpaths,
            [False, False, False, False, False]
        )
        self.assertEqual(
            branches,
            ['master', 'el6', 'f19', 'f20', 'f21']
        )
Esempio n. 24
0
class FedoraPlugin:
    """A plugin is a class which take the IrcBot as argument
    """

    requires = [
        'irc3.plugins.core',
        'irc3.plugins.command',
    ]

    def __init__(self, bot):
        self.bot = bot

        fas_url = bot.config['fas']['url']
        fas_username = bot.config['fas']['username']
        fas_password = bot.config['fas']['password']
        self.fasclient = AccountSystem(fas_url,
                                       username=fas_username,
                                       password=fas_password)

        #self.log.info("Downloading package owners cache")
        data = requests.get(
            'https://admin.fedoraproject.org/pkgdb/api/bugzilla?format=json',
            verify=True).json()
        self.bugzacl = data['bugzillaAcls']

        self.pkgdb = PkgDB()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)

    @staticmethod
    def _future_meetings(location):
        if not location.endswith('@irc.freenode.net'):
            location = '*****@*****.**' % location
        meetings = FedoraPlugin._query_fedocal(location=location)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            dt = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now < dt:
                yield dt, meeting

    @staticmethod
    def _meetings_for(calendar):
        meetings = FedoraPlugin._query_fedocal(calendar=calendar)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            start = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
            string = "%s %s" % (meeting['meeting_date_end'],
                                meeting['meeting_time_stop'])
            end = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now >= start and now <= end:
                yield meeting

    @staticmethod
    def _query_fedocal(**kwargs):
        url = 'https://apps.fedoraproject.org/calendar/api/meetings'
        return requests.get(url, params=kwargs).json()['meetings']

    @command
    def admins(self, mask, target, args):
        """admins <group name>

        Return the administrators list for the selected group

            %%admins <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    sponsors += person['username'] + ' '
            msg = 'Administrators for %s: %s' % (name, sponsors)
        except AppError:
            msg = 'There is no group %s.' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def badges(self, mask, target, args):
        """badges <username>

        Return badges statistics about a user.

            %%badges <username>
        """
        name = args['<username>']

        url = "https://badges.fedoraproject.org/user/" + name
        d = requests.get(url + "/json").json()

        if 'error' in d:
            response = d['error']
        else:
            template = "{name} has unlocked {n} Fedora Badges:  {url}"
            n = len(d['assertions'])
            response = template.format(name=name, url=url, n=n)

        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

    @command
    def branches(self, mask, target, args):
        """branches <package>

        Return the branches a package is in.

            %%branches <package>
        """
        package = args['<package>']

        try:
            pkginfo = self.pkgdb.get_package(package)
        except AppError:
            msg = "No such package exists."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        branch_list = []
        for listing in pkginfo['packages']:
            branch_list.append(listing['collection']['branchname'])
        branch_list.sort()
        msg = ' '.join(branch_list)
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def fas(self, mask, target, args):
        """fas <pattern>

        Searches a pattern in the list of FAS user

            %%fas <pattern>
        """
        users = self.fasclient.people_query(
            constraints={
                #'username': args['<pattern>'],
                'ircnick': args['<pattern>'],
            },
            columns=['username', 'ircnick', 'email'])
        if users:
            msg = ', '.join([
                '%s (%s) <%s>' % (user.username, user.ircnick, user.email)
                for user in users
            ])
        else:
            msg = 'No user matching found'
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def fasinfo(self, mask, target, args):
        """fasinfo <pattern>

        Return more information about the specified user

            %%fasinfo <username>
        """
        name = args['<username>']

        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Error getting info for user: "******"' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        if not person:
            msg = 'User "%s" doesn\'t exist' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        person['creation'] = person['creation'].split(' ')[0]
        string = ("User: %(username)s, Name: %(human_name)s"
                  ", email: %(email)s, Creation: %(creation)s"
                  ", IRC Nick: %(ircnick)s, Timezone: %(timezone)s"
                  ", Locale: %(locale)s"
                  ", GPG key ID: %(gpg_keyid)s, Status: %(status)s") % person
        self.bot.privmsg(target, '%s: %s' % (mask.nick, string))

        # List of unapproved groups is easy
        unapproved = ''
        for group in person['unapproved_memberships']:
            unapproved = unapproved + "%s " % group['name']
        if unapproved != '':
            msg = 'Unapproved Groups: %s' % unapproved
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

        # List of approved groups requires a separate query to extract roles
        constraints = {
            'username': name,
            'group': '%',
            'role_status': 'approved'
        }
        columns = ['username', 'group', 'role_type']
        roles = []
        try:
            roles = self.fasclient.people_query(constraints=constraints,
                                                columns=columns)
        except:
            msg = 'Error getting group memberships.'
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        approved = ''
        for role in roles:
            if role['role_type'] == 'sponsor':
                approved += '+' + role['group'] + ' '
            elif role['role_type'] == 'administrator':
                approved += '@' + role['group'] + ' '
            else:
                approved += role['group'] + ' '
        if approved == '':
            approved = "None"
        msg = 'Approved Groups: %s' % approved
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def group(self, mask, target, args):
        """group <group short name>

        Return information about a Fedora Account System group.

            %%group <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_by_name(name)
            msg = '%s: %s' % (name, group['display_name'])
        except AppError:
            msg = 'There is no group "%s".' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def hellomynameis(self, mask, target, args):
        """hellomynameis <username>

        Return brief information about a Fedora Account System username. Useful
        for things like meeting roll call and calling attention to yourself.

            %%hellomynameis <username>
        """
        name = args['<username>']
        msg = None
        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Something blew up, please try again'
        if not person:
            msg = 'Sorry, but you don\'t exist'
        else:
            msg = '%(username)s \'%(human_name)s\' <%(email)s>' % person

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def himynameis(self, mask, target, args):
        """himynameis <username>

        Return information about a Fedora Account System group.

            %%himynameis <username>
        """
        name = args['<username>']
        msg = None
        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Something blew up, please try again'
        if not person:
            msg = 'Sorry, but you don\'t exist'
        else:
            msg = '%(username)s \'Slim Shady\' <%(email)s>' % person

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def localtime(self, mask, target, args):
        """localtime <username>

        Returns the current time of the user.
        The timezone is queried from FAS.

            %%localtime <username>
        """
        name = args['<username>']

        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Error getting info user user: "******"' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        if not person:
            msg = 'User "%s" doesn\'t exist' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        timezone_name = person['timezone']
        if timezone_name is None:
            msg = 'User "%s" doesn\'t share his timezone' % name
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return
        try:
            time = datetime.datetime.now(pytz.timezone(timezone_name))
        except:
            msg = 'The timezone of "%s" was unknown: "%s"' % (name,
                                                              timezone_name)
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        msg = 'The current local time of "%s" is: "%s" (timezone: %s)' % (
            name, time.strftime('%H:%M'), timezone_name)
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def members(self, mask, target, args):
        """members <group short name>

        Return the list of members for the selected group

            %%members <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_members(name)
            members = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    members += '@' + person['username'] + ' '
                elif person['role_type'] == 'sponsor':
                    members += '+' + person['username'] + ' '
                else:
                    members += person['username'] + ' '
            msg = 'Members of %s: %s' % (name, members)
        except AppError:
            msg = 'There is no group %s.' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def nextmeeting(self, mask, target, args):
        """nextmeeting <channel>

        Return the next meeting scheduled for a particular channel.

            %%nextmeeting <channel>
        """
        channel = args['<channel>']

        channel = channel.strip('#').split('@')[0]
        meetings = sorted(self._future_meetings(channel), key=itemgetter(0))

        test, meetings = tee(meetings)
        try:
            test.next()
        except StopIteration:
            response = "There are no meetings scheduled for #%s." % channel
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            return

        for date, meeting in islice(meetings, 0, 3):
            response = "In #%s is %s (starting %s)" % (
                channel,
                meeting['meeting_name'],
                arrow.get(date).humanize(),
            )
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
        base = "https://apps.fedoraproject.org/calendar/location/"
        url = base + urllib.quote("%[email protected]/" % channel)
        self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))

    @command
    def nextmeetings(self, mask, target, args):
        """nextmeetings

        Return the next meetings scheduled for any channel(s).

            %%nextmeetings
        """
        msg = 'One moment, please...  Looking up the channel list.'
        self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

        url = 'https://apps.fedoraproject.org/calendar/api/locations/'
        locations = requests.get(url).json()['locations']
        meetings = sorted(chain(*[
            self._future_meetings(location) for location in locations
            if 'irc.freenode.net' in location
        ]),
                          key=itemgetter(0))

        test, meetings = tee(meetings)
        try:
            test.next()
        except StopIteration:
            response = "There are no meetings scheduled at all."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            return

        for date, meeting in islice(meetings, 0, 5):
            response = "In #%s is %s (starting %s)" % (
                meeting['meeting_location'].split('@')[0].strip(),
                meeting['meeting_name'],
                arrow.get(date).humanize(),
            )
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

    @command
    def pushduty(self, mask, target, args):
        """pushduty

        Return the list of people who are on releng push duty right now.

            %%pushduty
        """
        def get_persons():
            for meeting in self._meetings_for('release-engineering'):
                yield meeting['meeting_name']

        persons = list(get_persons())

        url = "https://apps.fedoraproject.org/" + \
            "calendar/release-engineering/"

        if not persons:
            response = "Nobody is listed as being on push duty right now..."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))
            return

        persons = ", ".join(persons)
        response = "The following people are on push duty: %s" % persons
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
        self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))

    @command
    def quote(self, mask, target, args):
        """quote <SYMBOL> [daily, weekly, monthly, quarterly]

        Return some datagrepper statistics on fedmsg categories.

            %%quote <symbol> <frame>
        """

        symbol = args['<symbol>']
        frame = 'daily'
        if '<frame>' in args:
            frame = args['<frame>']

        # Second, build a lookup table for symbols.  By default, we'll use the
        # fedmsg category names, take their first 3 characters and uppercase
        # them.  That will take things like "wiki" and turn them into "WIK" and
        # "bodhi" and turn them into "BOD".  This handles a lot for us.  We'll
        # then override those that don't make sense manually here.  For
        # instance "fedoratagger" by default would be "FED", but that's no
        # good.  We want "TAG".
        # Why all this trouble?  Well, as new things get added to the fedmsg
        # bus, we don't want to have keep coming back here and modifying this
        # code.  Hopefully this dance will at least partially future-proof us.
        symbols = dict([(processor.__name__.lower(),
                         processor.__name__[:3].upper())
                        for processor in fedmsg.meta.processors])
        symbols.update({
            'fedoratagger': 'TAG',
            'fedbadges': 'BDG',
            'buildsys': 'KOJ',
            'pkgdb': 'PKG',
            'meetbot': 'MTB',
            'planet': 'PLN',
            'trac': 'TRC',
            'mailman': 'MM3',
        })

        # Now invert the dict so we can lookup the argued symbol.
        # Yes, this is vulnerable to collisions.
        symbols = dict([(sym, name) for name, sym in symbols.items()])

        # These aren't user-facing topics, so drop 'em.
        del symbols['LOG']
        del symbols['UNH']
        del symbols['ANN']  # And this one is unused...

        key_fmt = lambda d: ', '.join(sorted(d.keys()))

        if symbol not in symbols:
            response = "No such symbol %r.  Try one of %s"
            msg = response % (symbol, key_fmt(symbols))
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        # Now, build another lookup of our various timeframes.
        frames = dict(
            daily=datetime.timedelta(days=1),
            weekly=datetime.timedelta(days=7),
            monthly=datetime.timedelta(days=30),
            quarterly=datetime.timedelta(days=91),
        )

        if frame not in frames:
            response = "No such timeframe %r.  Try one of %s"
            msg = response % (frame, key_fmt(frames))
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        category = [symbols[symbol]]

        t2 = datetime.datetime.utcnow()
        t1 = t2 - frames[frame]
        t0 = t1 - frames[frame]

        # Count the number of messages between t0 and t1, and between t1 and t2
        query1 = dict(start=t0, end=t1, category=category)
        query2 = dict(start=t1, end=t2, category=category)

        # Do this async for superfast datagrepper queries.
        tpool = ThreadPool()
        batched_values = tpool.map(datagrepper_query, [
            dict(start=x, end=y, category=category)
            for x, y in Utils.daterange(t1, t2, SPARKLINE_RESOLUTION)
        ] + [query1, query2])

        count2 = batched_values.pop()
        count1 = batched_values.pop()

        # Just rename the results.  We'll use the rest for the sparkline.
        sparkline_values = batched_values

        yester_phrases = dict(
            daily="yesterday",
            weekly="the week preceding this one",
            monthly="the month preceding this one",
            quarterly="the 3 months preceding these past three months",
        )
        phrases = dict(
            daily="24 hours",
            weekly="week",
            monthly="month",
            quarterly="3 months",
        )

        if count1 and count2:
            percent = ((float(count2) / count1) - 1) * 100
        elif not count1 and count2:
            # If the older of the two time periods had zero messages, but there
            # are some in the more current period.. well, that's an infinite
            # percent increase.
            percent = float('inf')
        elif not count1 and not count2:
            # If counts are zero for both periods, then the change is 0%.
            percent = 0
        else:
            # Else, if there were some messages in the old time period, but
            # none in the current... then that's a 100% drop off.
            percent = -100

        sign = lambda value: value >= 0 and '+' or '-'

        template = u"{sym}, {name} {sign}{percent:.2f}% over {phrase}"
        response = template.format(
            sym=symbol,
            name=symbols[symbol],
            sign=sign(percent),
            percent=abs(percent),
            phrase=yester_phrases[frame],
        )
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

        # Now, make a graph out of it.
        sparkline = Utils.sparkline(sparkline_values)

        template = u"     {sparkline}  ⤆ over {phrase}"
        response = template.format(sym=symbol,
                                   sparkline=sparkline,
                                   phrase=phrases[frame])
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

        to_utc = lambda t: time.gmtime(time.mktime(t.timetuple()))
        # And a final line for "x-axis tics"
        t1_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t1))
        t2_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t2))
        padding = u" " * (SPARKLINE_RESOLUTION - len(t1_fmt) - 3)
        template = u"     ↑ {t1}{padding}↑ {t2}"
        response = template.format(t1=t1_fmt, t2=t2_fmt, padding=padding)
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))

    @command
    def sponsors(self, mask, target, args):
        """sponsors <group short name>

        Return the sponsors list for the selected group

            %%sponsors <group_name>
        """
        name = args['<group_name>']

        msg = None
        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'sponsor':
                    sponsors += person['username'] + ' '
                elif person['role_type'] == 'administrator':
                    sponsors += '@' + person['username'] + ' '
            msg = 'Sponsors for %s: %s' % (name, sponsors)
        except AppError:
            msg = 'There is no group %s.' % name

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def vacation(self, mask, target, args):
        """vacation

        Return the list of people who are on vacation right now according
        to fedocal.

            %%vacation
        """
        def get_persons():
            for meeting in self._meetings_for('vacation'):
                for manager in meeting['meeting_manager']:
                    yield manager

        persons = list(get_persons())

        if not persons:
            response = "Nobody is listed as being on vacation right now..."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
            url = "https://apps.fedoraproject.org/calendar/vacation/"
            self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))
            return

        persons = ", ".join(persons)
        response = "The following people are on vacation: %s" % persons
        self.bot.privmsg(target, '%s: %s' % (mask.nick, response))
        url = "https://apps.fedoraproject.org/calendar/vacation/"
        self.bot.privmsg(target, '%s: - %s' % (mask.nick, url))

    @command
    def what(self, mask, target, args):
        """what <package>

        Returns a description of a given package.

            %%what <package>
        """
        package = args['<package>']
        msg = None
        try:
            summary = self.bugzacl['Fedora'][package]['summary']
            msg = "%s: %s" % (package, summary)
        except KeyError:
            msg = "No such package exists."

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def whoowns(self, mask, target, args):
        """whoowns <package>

        Return more information about the specified user

            %%whoowns <package>
        """

        package = args['<package>']

        try:
            mainowner = self.bugzacl['Fedora'][package]['owner']
        except KeyError:
            msg = "No such package exists."
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
            return

        others = []
        for key in self.bugzacl:
            if key == 'Fedora':
                continue
            try:
                owner = self.bugzacl[key][package]['owner']
                if owner == mainowner:
                    continue
            except KeyError:
                continue
            others.append("%s in %s" % (owner, key))

        if others == []:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, mainowner))
        else:
            msg = "%s (%s)" % (mainowner, ', '.join(others))
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))

    @command
    def wikilink(self, mask, target, args):
        """wikilink <username>

        Return MediaWiki link syntax for a FAS user's page on the wiki.

            %%wikilink <username>
        """
        name = args['<username>']

        person = msg = None
        try:
            person = self.fasclient.person_by_username(name)
        except:
            msg = 'Error getting info for user: "******"' % name
        if not person:
            msg = 'User "%s" doesn\'t exist' % name
        else:
            msg = "[[User:%s|%s]]" % (person["username"], person["human_name"]
                                      or '')

        if msg is not None:
            self.bot.privmsg(target, '%s: %s' % (mask.nick, msg))
Esempio n. 25
0
 def setUp(self):
     """ set up data used in the tests.
     setUp is called before each test function execution.
     """
     self.pkgdb = PkgDB(PKGDB_URL, insecure=True)
Esempio n. 26
0
class TestPkgdDB(unittest.TestCase):
    ''' Un-authenticated pkgdb2 tests. '''
    def setUp(self):
        """ set up data used in the tests.
        setUp is called before each test function execution.
        """
        self.pkgdb = PkgDB(PKGDB_URL, insecure=True)

    def test_get_collection(self):
        ''' Test the get_collections function. '''
        out = self.pkgdb.get_collections()
        self.assertEqual(sorted(out.keys()), ['collections', 'output'])
        self.assertTrue(len(out['collections']) >= 30)

        out = self.pkgdb.get_collections(pattern='f19')
        self.assertEqual(len(out['collections']), 1)
        self.assertEqual(out['collections'][0]['branchname'], 'f19')
        self.assertEqual(sorted(out.keys()), ['collections', 'output'])

        out = self.pkgdb.get_collections(
            clt_status=['EOL', 'Under Development'])
        self.assertEqual(sorted(out.keys()), ['collections', 'output'])
        self.assertTrue(len(out['collections']) >= 25)

    def test_get_package(self):
        ''' Test the get_package function. '''
        out = self.pkgdb.get_package('guake')
        self.assertEqual(sorted(out.keys()), ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 5)
        self.assertEqual(out['packages'][0]['collection']['branchname'],
                         'master')
        self.assertEqual(out['packages'][0]['package']['name'], 'guake')
        self.assertEqual(out['packages'][0]['point_of_contact'], 'pingou')

        out = self.pkgdb.get_package('guake', 'f20')
        self.assertEqual(sorted(out.keys()), ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 1)
        self.assertEqual(out['packages'][0]['collection']['branchname'], 'f20')
        self.assertEqual(out['packages'][0]['package']['name'], 'guake')
        out = self.pkgdb.get_package('guake', ['f20', 'f19'])
        self.assertEqual(sorted(out.keys()), ['output', 'packages'])
        self.assertEqual(out['output'], 'ok')
        self.assertEqual(len(out['packages']), 2)
        self.assertEqual(out['packages'][0]['collection']['branchname'], 'f19')
        self.assertEqual(out['packages'][1]['collection']['branchname'], 'f20')
        self.assertEqual(out['packages'][0]['package']['name'], 'guake')
        self.assertEqual(out['packages'][1]['package']['name'], 'guake')

    def test_get_packager_acls(self):
        ''' Test the get_packager_acls function. '''
        out = self.pkgdb.get_packager_acls('pingou')
        self.assertEqual(sorted(out.keys()),
                         ['acls', 'output', 'page', 'page_total'])
        self.assertEqual(len(out['acls']), 250)
        self.assertEqual(out['page_total'], 6)

        out = self.pkgdb.get_packager_acls('pingou', acls=['approveacls'])
        self.assertEqual(sorted(out.keys()),
                         ['acls', 'output', 'page', 'page_total'])
        self.assertTrue(len(out['acls']) >= 239)
        self.assertEqual(out['page_total'], 2)

        out = self.pkgdb.get_packager_acls('pingou', page=3)
        self.assertEqual(sorted(out.keys()),
                         ['acls', 'output', 'page', 'page_total'])
        self.assertTrue(len(out['acls']) >= 250)
        self.assertEqual(out['page_total'], 6)

        out = self.pkgdb.get_packager_acls('pingou', count=True)
        self.assertEqual(sorted(out.keys()),
                         ['acls_count', 'output', 'page', 'page_total'])
        self.assertTrue(out['acls_count'] >= 1043)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packager_acls('pingou', poc=True, count=True)
        self.assertEqual(sorted(out.keys()),
                         ['acls_count', 'output', 'page', 'page_total'])
        self.assertTrue(out['acls_count'] >= 750)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packager_acls('pingou', poc=False, count=True)
        self.assertEqual(sorted(out.keys()),
                         ['acls_count', 'output', 'page', 'page_total'])
        self.assertTrue(out['acls_count'] >= 239)
        self.assertEqual(out['page_total'], 1)

    def test_get_packager_stats(self):
        ''' Test the get_packager_stats function. '''
        out = self.pkgdb.get_packager_stats('pingou')
        self.assertEqual(
            sorted(out.keys()),
            ['el5', 'el6', 'epel7', 'f19', 'f20', 'f21', 'master', 'output'])
        self.assertEqual(sorted(out['master'].keys()),
                         ['co-maintainer', 'point of contact'])
        self.assertTrue(out['master']['point of contact'] >= 50)

    def test_get_packagers(self):
        ''' Test the get_packagers function. '''
        out = self.pkgdb.get_packagers('ping*')
        self.assertEqual(sorted(out.keys()), ['output', 'packagers'])
        self.assertEqual(out['packagers'], ['pingou'])

    def test_get_packages(self):
        ''' Test the get_packages function. '''
        out = self.pkgdb.get_packages('gua*')

        expected_keys = [
            'acls',
            'creation_date',
            'description',
            'monitor',
            'name',
            'review_url',
            'status',
            'summary',
            'upstream_url',
        ]

        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 10)
        self.assertEqual(out['packages'][0]['name'], 'guacamole-client')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['packages'][1]['name'], 'guacamole-common')
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', branches='el6')
        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 7)
        self.assertEqual(out['packages'][0]['name'], 'guacamole-common')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['packages'][1]['name'], 'guacamole-ext')
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', poc='pingou')
        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 1)
        self.assertEqual(out['packages'][0]['name'], 'guake')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', status='Retired')
        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 5)
        self.assertEqual(out['packages'][0]['name'], 'guacd')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('g*', orphaned=True)
        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertTrue(len(out['packages']) >= 44)
        self.assertEqual(out['packages'][0]['name'], 'ghex')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        #self.assertEqual(out['packages'][1]['name'], 'glom')
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('gua*', poc='pingou', acls=True)
        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 1)
        self.assertEqual(out['packages'][0]['name'], 'guake')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)

        out = self.pkgdb.get_packages('g*', page=2)
        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertEqual(len(out['packages']), 250)
        self.assertEqual(out['packages'][0]['name'], 'ghc-parameterized-data')
        self.assertEqual(sorted(out['packages'][0].keys()), expected_keys)
        self.assertEqual(out['page'], 2)
        self.assertEqual(out['page_total'], 6)

        out = self.pkgdb.get_packages('g*', count=True)
        self.assertEqual(sorted(out.keys()),
                         ['output', 'packages', 'page', 'page_total'])
        self.assertTrue(out['packages'] >= 1340)
        self.assertEqual(out['page'], 1)
        self.assertEqual(out['page_total'], 1)
Esempio n. 27
0
"""

from fedora.client import AppError, ServerError

import argparse
import requests
import logging
import koji

from pkgdb2client import PkgDB, PkgDBException, __version__
from pkgdb2client.cli import ActionError
import pkgdb2client
import pkgdb2client.utils as utils


PKGDBCLIENT = PkgDB("https://admin.fedoraproject.org/pkgdb", login_callback=pkgdb2client.ask_password)
BOLD = "\033[1m"
RED = "\033[0;31m"
RESET = "\033[0;0m"

# Initial simple logging stuff
logging.basicConfig()
PKGDBLOG = logging.getLogger("pkgdb2client")
LOG = logging.getLogger("pkgdb-admin")


def setup_parser():
    """
    Set the main arguments.
    """
    parser = argparse.ArgumentParser(prog="pkgdb-admin")
Esempio n. 28
0
# license.
"""

from fedora.client import (AppError, ServerError)

import argparse
import requests
import logging
import koji

from pkgdb2client import PkgDB, PkgDBException, __version__
from pkgdb2client.cli import ActionError
import pkgdb2client
import pkgdb2client.utils as utils

PKGDBCLIENT = PkgDB('https://admin.fedoraproject.org/pkgdb',
                    login_callback=pkgdb2client.ask_password)
BOLD = "\033[1m"
RED = "\033[0;31m"
RESET = "\033[0;0m"

# Initial simple logging stuff
logging.basicConfig()
PKGDBLOG = logging.getLogger("pkgdb2client")
LOG = logging.getLogger("pkgdb-admin")


def setup_parser():
    '''
    Set the main arguments.
    '''
    parser = argparse.ArgumentParser(prog="pkgdb-admin")
Esempio n. 29
0
# Set Koschei monitoring flag for <PACKAGES> to <VALUE> in pkgdb2 instance at
# <PKGDB_URL>.  Login credentials are provided in <FAS_CONF> file.
# Requires: packagedb-cli >= 2.9.
PACKAGES = ['pkg1', 'pkg2']
VALUE = True
PKGDB_URL = 'https://admin.stg.fedoraproject.org/pkgdb'
FAS_CONF = '/etc/fas.conf'


from pkgdb2client import PkgDB
from ConfigParser import ConfigParser

# Obtain FAS credentials
conf = ConfigParser()
conf.read(FAS_CONF)
login = conf.get('global', 'login')
password = conf.get('global', 'password')

# Initiate authenticated pkgdb2 session
pkgdb = PkgDB(PKGDB_URL)
pkgdb.login(login, password)

# Set package monitoring status one-by-one
for package in PACKAGES:
    result = pkgdb.set_koschei_status(package, VALUE)
    message = result.get('messages', 'Invalid output')
    print "%s: %s" % (package, message)

print "Done."
Esempio n. 30
0
class Fedora(callbacks.Plugin):
    """Use this plugin to retrieve Fedora-related information."""
    threaded = True

    def __init__(self, irc):
        super(Fedora, self).__init__(irc)

        # caches, automatically downloaded on __init__, manually refreshed on
        # .refresh
        self.userlist = None
        self.bugzacl = None

        # To get the information, we need a username and password to FAS.
        # DO NOT COMMIT YOUR USERNAME AND PASSWORD TO THE PUBLIC REPOSITORY!
        self.fasurl = self.registryValue('fas.url')
        self.username = self.registryValue('fas.username')
        self.password = self.registryValue('fas.password')

        self.fasclient = AccountSystem(self.fasurl,
                                       username=self.username,
                                       password=self.password)
        self.pkgdb = PkgDB()
        # URLs
        #self.url = {}

        # fetch necessary caches
        self._refresh()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)

    def _refresh(self):
        timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(None)
        self.log.info("Downloading user data")
        request = self.fasclient.send_request('/user/list',
                                              req_params={'search': '*'},
                                              auth=True,
                                              timeout=240)
        users = request['people'] + request['unapproved_people']
        del request
        self.log.info("Caching necessary user data")
        self.users = {}
        self.faslist = {}
        for user in users:
            name = user['username']
            self.users[name] = {}
            self.users[name]['id'] = user['id']
            key = ' '.join([
                user['username'], user['email'] or '', user['human_name']
                or '', user['ircnick'] or ''
            ])
            key = key.lower()
            value = "%s '%s' <%s>" % (user['username'], user['human_name']
                                      or '', user['email'] or '')
            self.faslist[key] = value
        self.log.info("Downloading package owners cache")
        data = requests.get(
            'https://admin.fedoraproject.org/pkgdb/api/bugzilla?format=json',
            verify=True).json()
        self.bugzacl = data['bugzillaAcls']
        socket.setdefaulttimeout(timeout)

    def refresh(self, irc, msg, args):
        """takes no arguments

        Refresh the necessary caches."""
        self._refresh()
        irc.replySuccess()

    refresh = wrap(refresh)

    def _load_json(self, url):
        timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(45)
        json = simplejson.loads(utils.web.getUrl(url))
        socket.setdefaulttimeout(timeout)
        return json

    def whoowns(self, irc, msg, args, package):
        """<package>

        Retrieve the owner of a given package
        """
        try:
            mainowner = self.bugzacl['Fedora'][package]['owner']
        except KeyError:
            irc.reply("No such package exists.")
            return
        others = []
        for key in self.bugzacl:
            if key == 'Fedora':
                continue
            try:
                owner = self.bugzacl[key][package]['owner']
                if owner == mainowner:
                    continue
            except KeyError:
                continue
            others.append("%s in %s" % (owner, key))
        if others == []:
            irc.reply(mainowner)
        else:
            irc.reply("%s (%s)" % (mainowner, ', '.join(others)))

    whoowns = wrap(whoowns, ['text'])

    def branches(self, irc, msg, args, package):
        """<package>

        Return the branches a package is in."""
        try:
            pkginfo = self.pkgdb.get_package(package)
        except AppError:
            irc.reply("No such package exists.")
            return
        branch_list = []
        for listing in pkginfo['packages']:
            branch_list.append(listing['collection']['branchname'])
        branch_list.sort()
        irc.reply(' '.join(branch_list))
        return

    branches = wrap(branches, ['text'])

    def what(self, irc, msg, args, package):
        """<package>

        Returns a description of a given package.
        """
        try:
            summary = self.bugzacl['Fedora'][package]['summary']
            irc.reply("%s: %s" % (package, summary))
        except KeyError:
            irc.reply("No such package exists.")
            return

    what = wrap(what, ['text'])

    def fas(self, irc, msg, args, find_name):
        """<query>

        Search the Fedora Account System usernames, full names, and email
        addresses for a match."""
        find_name = to_unicode(find_name)
        matches = []
        for entry in self.faslist.keys():
            if entry.find(find_name.lower()) != -1:
                matches.append(entry)
        if len(matches) == 0:
            irc.reply("'%s' Not Found!" % find_name)
        else:
            output = []
            for match in matches:
                output.append(self.faslist[match])
            irc.reply(' - '.join(output).encode('utf-8'))

    fas = wrap(fas, ['text'])

    def hellomynameis(self, irc, msg, args, name):
        """<username>

        Return brief information about a Fedora Account System username. Useful
        for things like meeting roll call and calling attention to yourself."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Something blew up, please try again')
            return
        if not person:
            irc.reply('Sorry, but you don\'t exist')
            return
        irc.reply(('%(username)s \'%(human_name)s\' <%(email)s>' %
                   person).encode('utf-8'))

    hellomynameis = wrap(hellomynameis, ['text'])

    def himynameis(self, irc, msg, args, name):
        """<username>

        Will the real Slim Shady please stand up?"""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Something blew up, please try again')
            return
        if not person:
            irc.reply('Sorry, but you don\'t exist')
            return
        irc.reply(('%(username)s \'Slim Shady\' <%(email)s>' %
                   person).encode('utf-8'))

    himynameis = wrap(himynameis, ['text'])

    def localtime(self, irc, msg, args, name):
        """<username>

        Returns the current time of the user.
        The timezone is queried from FAS."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info user user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        timezone_name = person['timezone']
        if timezone_name is None:
            irc.reply('User "%s" doesn\'t share his timezone' % name)
            return
        try:
            time = datetime.datetime.now(pytz.timezone(timezone_name))
        except:
            irc.reply('The timezone of "%s" was unknown: "%s"' %
                      (name, timezone))
            return
        irc.reply('The current local time of "%s" is: "%s" (timezone: %s)' %
                  (name, time.strftime('%H:%M'), timezone_name))

    localtime = wrap(localtime, ['text'])

    def fasinfo(self, irc, msg, args, name):
        """<username>

        Return information on a Fedora Account System username."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info for user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        person['creation'] = person['creation'].split(' ')[0]
        string = ("User: %(username)s, Name: %(human_name)s"
                  ", email: %(email)s, Creation: %(creation)s"
                  ", IRC Nick: %(ircnick)s, Timezone: %(timezone)s"
                  ", Locale: %(locale)s"
                  ", GPG key ID: %(gpg_keyid)s, Status: %(status)s") % person
        irc.reply(string.encode('utf-8'))

        # List of unapproved groups is easy
        unapproved = ''
        for group in person['unapproved_memberships']:
            unapproved = unapproved + "%s " % group['name']
        if unapproved != '':
            irc.reply('Unapproved Groups: %s' % unapproved)

        # List of approved groups requires a separate query to extract roles
        constraints = {
            'username': name,
            'group': '%',
            'role_status': 'approved'
        }
        columns = ['username', 'group', 'role_type']
        roles = []
        try:
            roles = self.fasclient.people_query(constraints=constraints,
                                                columns=columns)
        except:
            irc.reply('Error getting group memberships.')
            return

        approved = ''
        for role in roles:
            if role['role_type'] == 'sponsor':
                approved += '+' + role['group'] + ' '
            elif role['role_type'] == 'administrator':
                approved += '@' + role['group'] + ' '
            else:
                approved += role['group'] + ' '
        if approved == '':
            approved = "None"

        irc.reply('Approved Groups: %s' % approved)

    fasinfo = wrap(fasinfo, ['text'])

    def group(self, irc, msg, args, name):
        """<group short name>

        Return information about a Fedora Account System group."""
        try:
            group = self.fasclient.group_by_name(name)
            irc.reply('%s: %s' % (name, group['display_name']))
        except AppError:
            irc.reply('There is no group "%s".' % name)

    group = wrap(group, ['text'])

    def admins(self, irc, msg, args, name):
        """<group short name>

        Return the administrators list for the selected group"""

        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    sponsors += person['username'] + ' '
            irc.reply('Administrators for %s: %s' % (name, sponsors))
        except AppError:
            irc.reply('There is no group %s.' % name)

    admins = wrap(admins, ['text'])

    def sponsors(self, irc, msg, args, name):
        """<group short name>

        Return the sponsors list for the selected group"""

        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'sponsor':
                    sponsors += person['username'] + ' '
                elif person['role_type'] == 'administrator':
                    sponsors += '@' + person['username'] + ' '
            irc.reply('Sponsors for %s: %s' % (name, sponsors))
        except AppError:
            irc.reply('There is no group %s.' % name)

    sponsors = wrap(sponsors, ['text'])

    def members(self, irc, msg, args, name):
        """<group short name>

        Return a list of members of the specified group"""
        try:
            group = self.fasclient.group_members(name)
            members = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    members += '@' + person['username'] + ' '
                elif person['role_type'] == 'sponsor':
                    members += '+' + person['username'] + ' '
                else:
                    members += person['username'] + ' '
            irc.reply('Members of %s: %s' % (name, members))
        except AppError:
            irc.reply('There is no group %s.' % name)

    members = wrap(members, ['text'])

    def showticket(self, irc, msg, args, baseurl, number):
        """<baseurl> <number>

        Return the name and URL of a trac ticket or bugzilla bug.
        """
        url = format(baseurl, str(number))
        size = conf.supybot.protocols.http.peekSize()
        text = utils.web.getUrl(url, size=size)
        parser = Title()
        try:
            parser.feed(text)
        except sgmllib.SGMLParseError:
            irc.reply(format('Encountered a problem parsing %u', url))
        if parser.title:
            irc.reply(utils.web.htmlToText(parser.title.strip()) + ' - ' + url)
        else:
            irc.reply(
                format(
                    'That URL appears to have no HTML title ' +
                    'within the first %i bytes.', size))

    showticket = wrap(showticket, ['httpUrl', 'int'])

    def swedish(self, irc, msg, args):
        """takes no arguments

        Humor mmcgrath."""

        # Import this here to avoid a circular import problem.
        from __init__ import __version__

        irc.reply(str('kwack kwack'))
        irc.reply(str('bork bork bork'))
        irc.reply(str('(supybot-fedora version %s)' % __version__))

    swedish = wrap(swedish)

    def wikilink(self, irc, msg, args, name):
        """<username>

        Return MediaWiki link syntax for a FAS user's page on the wiki."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info for user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        string = "[[User:%s|%s]]" % (person["username"], person["human_name"]
                                     or '')
        irc.reply(string.encode('utf-8'))

    wikilink = wrap(wikilink, ['text'])

    def mirroradmins(self, irc, msg, args, hostname):
        """<hostname>

        Return MirrorManager list of FAS usernames which administer <hostname>.
        <hostname> must be the FQDN of the host."""
        url = ("https://admin.fedoraproject.org/mirrormanager/mirroradmins?"
               "tg_format=json&host=" + hostname)
        result = self._load_json(url)['values']
        if len(result) == 0:
            irc.reply('Hostname "%s" not found' % hostname)
            return
        string = 'Mirror Admins of %s: ' % hostname
        string += ' '.join(result)
        irc.reply(string.encode('utf-8'))

    mirroradmins = wrap(mirroradmins, ['text'])

    def nextmeeting(self, irc, msg, args, channel):
        """<channel>

        Return the next meeting scheduled for a particular channel.
        """

        channel = channel.strip('#').split('@')[0]
        meetings = list(self._future_meetings(channel))
        if not meetings:
            response = "There are no meetings scheduled for #%s." % channel
            irc.reply(response.encode('utf-8'))
            return

        date, meeting = meetings[0]
        response = "The next meeting in #%s is %s (starting %s)" % (
            channel,
            meeting['meeting_name'],
            arrow.get(date).humanize(),
        )
        irc.reply(response.encode('utf-8'))
        base = "https://apps.fedoraproject.org/calendar/location/"
        url = base + urllib.quote("%[email protected]/" % channel)
        irc.reply("- " + url.encode('utf-8'))

    nextmeeting = wrap(nextmeeting, ['text'])

    @staticmethod
    def _future_meetings(channel):
        response = requests.get(
            'https://apps.fedoraproject.org/calendar/api/meetings',
            params=dict(location='*****@*****.**' % channel, ))

        data = response.json()
        now = datetime.datetime.utcnow()

        for meeting in data['meetings']:
            string = meeting['meeting_date'] + " " + meeting[
                'meeting_time_start']
            dt = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now < dt:
                yield dt, meeting

    def badges(self, irc, msg, args, name):
        """<username>

        Return badges statistics about a user.
        """
        url = "https://badges.fedoraproject.org/user/" + name
        d = requests.get(url + "/json").json()

        if 'error' in d:
            response = d['error']
        else:
            template = "{name} has unlocked {n} Fedora Badges:  {url}"
            n = len(d['assertions'])
            response = template.format(name=name, url=url, n=n)

        irc.reply(response.encode('utf-8'))

    badges = wrap(badges, ['text'])

    def quote(self, irc, msg, args, arguments):
        """<SYMBOL> [daily, weekly, monthly, quarterly]

        Return some datagrepper statistics on fedmsg categories.
        """

        # First, some argument parsing.  Supybot should be able to do this for
        # us, but I couldn't figure it out.  The supybot.plugins.additional
        # object is the thing to use... except its weird.
        tokens = arguments.split(None, 1)
        if len(tokens) == 1:
            symbol, frame = tokens[0], 'daily'
        else:
            symbol, frame = tokens

        # Second, build a lookup table for symbols.  By default, we'll use the
        # fedmsg category names, take their first 3 characters and uppercase
        # them.  That will take things like "wiki" and turn them into "WIK" and
        # "bodhi" and turn them into "BOD".  This handles a lot for us.  We'll
        # then override those that don't make sense manually here.  For
        # instance "fedoratagger" by default would be "FED", but that's no
        # good.  We want "TAG".
        # Why all this trouble?  Well, as new things get added to the fedmsg
        # bus, we don't want to have keep coming back here and modifying this
        # code.  Hopefully this dance will at least partially future-proof us.
        symbols = dict([(processor.__name__.lower(),
                         processor.__name__[:3].upper())
                        for processor in fedmsg.meta.processors])
        symbols.update({
            'fedoratagger': 'TAG',
            'fedbadges': 'BDG',
            'buildsys': 'KOJ',
            'pkgdb': 'PKG',
            'meetbot': 'MTB',
            'planet': 'PLN',
            'trac': 'TRC',
            'mailman': 'MM3',
        })

        # Now invert the dict so we can lookup the argued symbol.
        # Yes, this is vulnerable to collisions.
        symbols = dict([(sym, name) for name, sym in symbols.items()])

        # These aren't user-facing topics, so drop 'em.
        del symbols['LOG']
        del symbols['UNH']
        del symbols['ANN']  # And this one is unused...

        key_fmt = lambda d: ', '.join(sorted(d.keys()))

        if not symbol in symbols:
            response = "No such symbol %r.  Try one of %s"
            irc.reply((response % (symbol, key_fmt(symbols))).encode('utf-8'))
            return

        # Now, build another lookup of our various timeframes.
        frames = dict(
            daily=datetime.timedelta(days=1),
            weekly=datetime.timedelta(days=7),
            monthly=datetime.timedelta(days=30),
            quarterly=datetime.timedelta(days=91),
        )

        if not frame in frames:
            response = "No such timeframe %r.  Try one of %s"
            irc.reply((response % (frame, key_fmt(frames))).encode('utf-8'))
            return

        category = [symbols[symbol]]

        t2 = datetime.datetime.now()
        t1 = t2 - frames[frame]
        t0 = t1 - frames[frame]

        # Count the number of messages between t0 and t1, and between t1 and t2
        query1 = dict(start=t0, end=t1, category=category)
        query2 = dict(start=t1, end=t2, category=category)

        # Do this async for superfast datagrepper queries.
        tpool = ThreadPool()
        batched_values = tpool.map(datagrepper_query, [
            dict(start=x, end=y, category=category)
            for x, y in Utils.daterange(t1, t2, SPARKLINE_RESOLUTION)
        ] + [query1, query2])

        count2 = batched_values.pop()
        count1 = batched_values.pop()

        # Just rename the results.  We'll use the rest for the sparkline.
        sparkline_values = batched_values

        yester_phrases = dict(
            daily="yesterday",
            weekly="the week preceding this one",
            monthly="the month preceding this one",
            quarterly="the 3 months preceding these past three months",
        )
        phrases = dict(
            daily="24 hours",
            weekly="week",
            monthly="month",
            quarterly="3 months",
        )

        if count1 and count2:
            percent = ((float(count2) / count1) - 1) * 100
        elif not count1 and count2:
            # If the older of the two time periods had zero messages, but there
            # are some in the more current period.. well, that's an infinite
            # percent increase.
            percent = float('inf')
        elif not count1 and not count2:
            # If counts are zero for both periods, then the change is 0%.
            percent = 0
        else:
            # Else, if there were some messages in the old time period, but
            # none in the current... then that's a 100% drop off.
            percent = -100

        sign = lambda value: value >= 0 and '+' or '-'

        template = u"{sym}, {name} {sign}{percent:.2f}% over {phrase}"
        response = template.format(
            sym=symbol,
            name=symbols[symbol],
            sign=sign(percent),
            percent=abs(percent),
            phrase=yester_phrases[frame],
        )
        irc.reply(response.encode('utf-8'))

        # Now, make a graph out of it.
        sparkline = Utils.sparkline(sparkline_values)

        template = u"     {sparkline}  ⤆ over {phrase}"
        response = template.format(sym=symbol,
                                   sparkline=sparkline,
                                   phrase=phrases[frame])
        irc.reply(response.encode('utf-8'))

        to_utc = lambda t: time.gmtime(time.mktime(t.timetuple()))
        # And a final line for "x-axis tics"
        t1_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t1))
        t2_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t2))
        padding = u" " * (SPARKLINE_RESOLUTION - len(t1_fmt) - 3)
        template = u"     ↑ {t1}{padding}↑ {t2}"
        response = template.format(t1=t1_fmt, t2=t2_fmt, padding=padding)
        irc.reply(response.encode('utf-8'))

    quote = wrap(quote, ['text'])
Esempio n. 31
0
class Fedora(callbacks.Plugin):
    """Use this plugin to retrieve Fedora-related information."""
    threaded = True

    def __init__(self, irc):
        super(Fedora, self).__init__(irc)

        # caches, automatically downloaded on __init__, manually refreshed on
        # .refresh
        self.userlist = None
        self.bugzacl = None

        # To get the information, we need a username and password to FAS.
        # DO NOT COMMIT YOUR USERNAME AND PASSWORD TO THE PUBLIC REPOSITORY!
        self.fasurl = self.registryValue('fas.url')
        self.username = self.registryValue('fas.username')
        self.password = self.registryValue('fas.password')

        self.fasclient = AccountSystem(self.fasurl, username=self.username,
                                       password=self.password)
        self.pkgdb = PkgDB()
        # URLs
        # self.url = {}

        self.github_oauth_token = self.registryValue('github.oauth_token')

        # fetch necessary caches
        self._refresh()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)

    def _refresh(self):
        timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(None)
        self.log.info("Downloading user data")
        request = self.fasclient.send_request('/user/list',
                                              req_params={'search': '*'},
                                              auth=True,
                                              timeout=240)
        users = request['people'] + request['unapproved_people']
        del request
        self.log.info("Caching necessary user data")
        self.users = {}
        self.faslist = {}
        for user in users:
            name = user['username']
            self.users[name] = {}
            self.users[name]['id'] = user['id']
            key = ' '.join([user['username'], user['email'] or '',
                            user['human_name'] or '', user['ircnick'] or ''])
            key = key.lower()
            value = "%s '%s' <%s>" % (user['username'], user['human_name'] or
                                      '', user['email'] or '')
            self.faslist[key] = value
        self.log.info("Downloading package owners cache")
        data = requests.get(
            'https://admin.fedoraproject.org/pkgdb/api/bugzilla?format=json',
            verify=True).json()
        self.bugzacl = data['bugzillaAcls']
        socket.setdefaulttimeout(timeout)

    def refresh(self, irc, msg, args):
        """takes no arguments

        Refresh the necessary caches."""
        self._refresh()
        irc.replySuccess()
    refresh = wrap(refresh)

    def _load_json(self, url):
        timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(45)
        json = simplejson.loads(utils.web.getUrl(url))
        socket.setdefaulttimeout(timeout)
        return json

    def pulls(self, irc, msg, args, slug):
        """<username/repo>

        List the pending pull requests on a github repo.
        """

        if slug.count('/') != 1:
            irc.reply('Must be GitHub repo of the format <username/repo>')
            return

        username, repo = slug.strip().split('/')

        tmpl = "https://api.github.com/repos/{username}/{repo}/" + \
            "pulls?per_page=100"
        url = tmpl.format(username=username, repo=repo)
        auth = dict(access_token=self.github_oauth_token)

        results = []
        link = dict(next=url)
        while 'next' in link:
            response = requests.get(link['next'], params=auth)

            if response.status_code == 404:
                irc.reply('No such GitHub repository %r' % slug)
                return

            # And.. if we didn't get good results, just bail.
            if response.status_code != 200:
                raise IOError(
                    "Non-200 status code %r; %r; %r" % (
                        response.status_code, url, response.json))

            if callable(response.json):
                # Newer python-requests
                results += response.json()
            else:
                # Older python-requests
                results += response.json

            field = response.headers.get('link', None)

            link = dict()
            if field:
                link = dict([
                    (
                        part.split('; ')[1][5:-1],
                        part.split('; ')[0][1:-1],
                    ) for part in field.split(', ')
                ])

        if not results:
            irc.reply('No pending pull requests on {slug}'.format(slug=slug))
        else:
            n = 4
            for pull in results[:n]:
                irc.reply('@{user}\'s "{title}" {url}'.format(
                    user=pull['user']['login'],
                    title=pull['title'],
                    url=pull['html_url']))

            if len(results) > n:
                irc.reply('... and %i more.' % (len(results) - n))


    pulls = wrap(pulls, ['text'])

    def whoowns(self, irc, msg, args, package):
        """<package>

        Retrieve the owner of a given package
        """
        try:
            mainowner = self.bugzacl['Fedora'][package]['owner']
        except KeyError:
            irc.reply("No such package exists.")
            return
        others = []
        for key in self.bugzacl:
            if key == 'Fedora':
                continue
            try:
                owner = self.bugzacl[key][package]['owner']
                if owner == mainowner:
                    continue
            except KeyError:
                continue
            others.append("%s in %s" % (owner, key))
        if others == []:
            irc.reply(mainowner)
        else:
            irc.reply("%s (%s)" % (mainowner, ', '.join(others)))
    whoowns = wrap(whoowns, ['text'])

    def branches(self, irc, msg, args, package):
        """<package>

        Return the branches a package is in."""
        try:
            pkginfo = self.pkgdb.get_package(package)
        except AppError:
            irc.reply("No such package exists.")
            return
        branch_list = []
        for listing in pkginfo['packages']:
            branch_list.append(listing['collection']['branchname'])
        branch_list.sort()
        irc.reply(' '.join(branch_list))
        return
    branches = wrap(branches, ['text'])

    def what(self, irc, msg, args, package):
        """<package>

        Returns a description of a given package.
        """
        try:
            summary = self.bugzacl['Fedora'][package]['summary']
            irc.reply("%s: %s" % (package, summary))
        except KeyError:
            irc.reply("No such package exists.")
            return
    what = wrap(what, ['text'])

    def fas(self, irc, msg, args, find_name):
        """<query>

        Search the Fedora Account System usernames, full names, and email
        addresses for a match."""
        find_name = to_unicode(find_name)
        matches = []
        for entry in self.faslist.keys():
            if entry.find(find_name.lower()) != -1:
                matches.append(entry)
        if len(matches) == 0:
            irc.reply("'%s' Not Found!" % find_name)
        else:
            output = []
            for match in matches:
                output.append(self.faslist[match])
            irc.reply(' - '.join(output).encode('utf-8'))
    fas = wrap(fas, ['text'])

    def hellomynameis(self, irc, msg, args, name):
        """<username>

        Return brief information about a Fedora Account System username. Useful
        for things like meeting roll call and calling attention to yourself."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Something blew up, please try again')
            return
        if not person:
            irc.reply('Sorry, but you don\'t exist')
            return
        irc.reply(('%(username)s \'%(human_name)s\' <%(email)s>' %
                   person).encode('utf-8'))
    hellomynameis = wrap(hellomynameis, ['text'])

    def himynameis(self, irc, msg, args, name):
        """<username>

        Will the real Slim Shady please stand up?"""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Something blew up, please try again')
            return
        if not person:
            irc.reply('Sorry, but you don\'t exist')
            return
        irc.reply(('%(username)s \'Slim Shady\' <%(email)s>' %
                   person).encode('utf-8'))
    himynameis = wrap(himynameis, ['text'])

    def localtime(self, irc, msg, args, name):
        """<username>

        Returns the current time of the user.
        The timezone is queried from FAS."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info user user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        timezone_name = person['timezone']
        if timezone_name is None:
            irc.reply('User "%s" doesn\'t share his timezone' % name)
            return
        try:
            time = datetime.datetime.now(pytz.timezone(timezone_name))
        except:
            irc.reply('The timezone of "%s" was unknown: "%s"' % (name,
                                                                  timezone))
            return
        irc.reply('The current local time of "%s" is: "%s" (timezone: %s)' %
                  (name, time.strftime('%H:%M'), timezone_name))
    localtime = wrap(localtime, ['text'])

    def fasinfo(self, irc, msg, args, name):
        """<username>

        Return information on a Fedora Account System username."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info for user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        person['creation'] = person['creation'].split(' ')[0]
        string = ("User: %(username)s, Name: %(human_name)s"
                  ", email: %(email)s, Creation: %(creation)s"
                  ", IRC Nick: %(ircnick)s, Timezone: %(timezone)s"
                  ", Locale: %(locale)s"
                  ", GPG key ID: %(gpg_keyid)s, Status: %(status)s") % person
        irc.reply(string.encode('utf-8'))

        # List of unapproved groups is easy
        unapproved = ''
        for group in person['unapproved_memberships']:
            unapproved = unapproved + "%s " % group['name']
        if unapproved != '':
            irc.reply('Unapproved Groups: %s' % unapproved)

        # List of approved groups requires a separate query to extract roles
        constraints = {'username': name, 'group': '%',
                       'role_status': 'approved'}
        columns = ['username', 'group', 'role_type']
        roles = []
        try:
            roles = self.fasclient.people_query(constraints=constraints,
                                                columns=columns)
        except:
            irc.reply('Error getting group memberships.')
            return

        approved = ''
        for role in roles:
            if role['role_type'] == 'sponsor':
                approved += '+' + role['group'] + ' '
            elif role['role_type'] == 'administrator':
                approved += '@' + role['group'] + ' '
            else:
                approved += role['group'] + ' '
        if approved == '':
            approved = "None"

        irc.reply('Approved Groups: %s' % approved)
    fasinfo = wrap(fasinfo, ['text'])

    def group(self, irc, msg, args, name):
        """<group short name>

        Return information about a Fedora Account System group."""
        try:
            group = self.fasclient.group_by_name(name)
            irc.reply('%s: %s' %
                      (name, group['display_name']))
        except AppError:
            irc.reply('There is no group "%s".' % name)
    group = wrap(group, ['text'])

    def admins(self, irc, msg, args, name):
        """<group short name>

        Return the administrators list for the selected group"""

        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    sponsors += person['username'] + ' '
            irc.reply('Administrators for %s: %s' % (name, sponsors))
        except AppError:
            irc.reply('There is no group %s.' % name)

    admins = wrap(admins, ['text'])

    def sponsors(self, irc, msg, args, name):
        """<group short name>

        Return the sponsors list for the selected group"""

        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'sponsor':
                    sponsors += person['username'] + ' '
                elif person['role_type'] == 'administrator':
                    sponsors += '@' + person['username'] + ' '
            irc.reply('Sponsors for %s: %s' % (name, sponsors))
        except AppError:
            irc.reply('There is no group %s.' % name)

    sponsors = wrap(sponsors, ['text'])

    def members(self, irc, msg, args, name):
        """<group short name>

        Return a list of members of the specified group"""
        try:
            group = self.fasclient.group_members(name)
            members = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    members += '@' + person['username'] + ' '
                elif person['role_type'] == 'sponsor':
                    members += '+' + person['username'] + ' '
                else:
                    members += person['username'] + ' '
            irc.reply('Members of %s: %s' % (name, members))
        except AppError:
            irc.reply('There is no group %s.' % name)

    members = wrap(members, ['text'])

    def showticket(self, irc, msg, args, baseurl, number):
        """<baseurl> <number>

        Return the name and URL of a trac ticket or bugzilla bug.
        """
        url = format(baseurl, str(number))
        size = conf.supybot.protocols.http.peekSize()
        text = utils.web.getUrl(url, size=size)
        parser = Title()
        try:
            parser.feed(text)
        except sgmllib.SGMLParseError:
            irc.reply(format('Encountered a problem parsing %u', url))
        if parser.title:
            irc.reply(utils.web.htmlToText(parser.title.strip()) + ' - ' + url)
        else:
            irc.reply(format('That URL appears to have no HTML title ' +
                             'within the first %i bytes.', size))
    showticket = wrap(showticket, ['httpUrl', 'int'])

    def swedish(self, irc, msg, args):
        """takes no arguments

        Humor mmcgrath."""

        # Import this here to avoid a circular import problem.
        from __init__ import __version__

        irc.reply(str('kwack kwack'))
        irc.reply(str('bork bork bork'))
        irc.reply(str('(supybot-fedora version %s)' % __version__))
    swedish = wrap(swedish)

    def wikilink(self, irc, msg, args, name):
        """<username>

        Return MediaWiki link syntax for a FAS user's page on the wiki."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info for user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        string = "[[User:%s|%s]]" % (person["username"],
                                     person["human_name"] or '')
        irc.reply(string.encode('utf-8'))
    wikilink = wrap(wikilink, ['text'])

    def mirroradmins(self, irc, msg, args, hostname):
        """<hostname>

        Return MirrorManager list of FAS usernames which administer <hostname>.
        <hostname> must be the FQDN of the host."""
        url = ("https://admin.fedoraproject.org/mirrormanager/mirroradmins?"
               "tg_format=json&host=" + hostname)
        result = self._load_json(url)['values']
        if len(result) == 0:
            irc.reply('Hostname "%s" not found' % hostname)
            return
        string = 'Mirror Admins of %s: ' % hostname
        string += ' '.join(result)
        irc.reply(string.encode('utf-8'))
    mirroradmins = wrap(mirroradmins, ['text'])

    def pushduty(self, irc, msg, args):
        """

        Return the list of people who are on releng push duty right now.
        """

        def get_persons():
            for meeting in self._meetings_for('release-engineering'):
                yield meeting['meeting_name']

        persons = list(get_persons())

        url = "https://apps.fedoraproject.org/" + \
            "calendar/release-engineering/"

        if not persons:
            response = "Nobody is listed as being on push duty right now..."
            irc.reply(response.encode('utf-8'))
            irc.reply("- " + url.encode('utf-8'))
            return

        persons = ", ".join(persons)
        response = "The following people are on push duty: %s" % persons
        irc.reply(response.encode('utf-8'))
        irc.reply("- " + url.encode('utf-8'))
    pushduty = wrap(pushduty)

    def vacation(self, irc, msg, args):
        """

        Return the list of people who are on vacation right now.
        """

        def get_persons():
            for meeting in self._meetings_for('vacation'):
                for manager in meeting['meeting_manager']:
                    yield manager

        persons = list(get_persons())

        if not persons:
            response = "Nobody is listed as being on vacation right now..."
            irc.reply(response.encode('utf-8'))
            url = "https://apps.fedoraproject.org/calendar/vacation/"
            irc.reply("- " + url.encode('utf-8'))
            return

        persons = ", ".join(persons)
        response = "The following people are on vacation: %s" % persons
        irc.reply(response.encode('utf-8'))
        url = "https://apps.fedoraproject.org/calendar/vacation/"
        irc.reply("- " + url.encode('utf-8'))
    vacation = wrap(vacation)

    def nextmeeting(self, irc, msg, args, channel):
        """<channel>

        Return the next meeting scheduled for a particular channel.
        """

        channel = channel.strip('#').split('@')[0]
        meetings = list(self._future_meetings(channel))
        if not meetings:
            response = "There are no meetings scheduled for #%s." % channel
            irc.reply(response.encode('utf-8'))
            return

        date, meeting = meetings[0]
        response = "The next meeting in #%s is %s (starting %s)" % (
            channel,
            meeting['meeting_name'],
            arrow.get(date).humanize(),
        )
        irc.reply(response.encode('utf-8'))
        base = "https://apps.fedoraproject.org/calendar/location/"
        url = base + urllib.quote("%[email protected]/" % channel)
        irc.reply("- " + url.encode('utf-8'))
    nextmeeting = wrap(nextmeeting, ['text'])

    @staticmethod
    def _future_meetings(channel):
        location = '*****@*****.**' % channel
        meetings = Fedora._query_fedocal(location=location)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            dt = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now < dt:
                yield dt, meeting

    @staticmethod
    def _meetings_for(calendar):
        meetings = Fedora._query_fedocal(calendar=calendar)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            start = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
            string = "%s %s" % (meeting['meeting_date_end'],
                                meeting['meeting_time_stop'])
            end = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now >= start and now <= end:
                yield meeting

    @staticmethod
    def _query_fedocal(**kwargs):
        url = 'https://apps.fedoraproject.org/calendar/api/meetings'
        return requests.get(url, params=kwargs).json()['meetings']

    def badges(self, irc, msg, args, name):
        """<username>

        Return badges statistics about a user.
        """
        url = "https://badges.fedoraproject.org/user/" + name
        d = requests.get(url + "/json").json()

        if 'error' in d:
            response = d['error']
        else:
            template = "{name} has unlocked {n} Fedora Badges:  {url}"
            n = len(d['assertions'])
            response = template.format(name=name, url=url, n=n)

        irc.reply(response.encode('utf-8'))
    badges = wrap(badges, ['text'])

    def quote(self, irc, msg, args, arguments):
        """<SYMBOL> [daily, weekly, monthly, quarterly]

        Return some datagrepper statistics on fedmsg categories.
        """

        # First, some argument parsing.  Supybot should be able to do this for
        # us, but I couldn't figure it out.  The supybot.plugins.additional
        # object is the thing to use... except its weird.
        tokens = arguments.split(None, 1)
        if len(tokens) == 1:
            symbol, frame = tokens[0], 'daily'
        else:
            symbol, frame = tokens

        # Second, build a lookup table for symbols.  By default, we'll use the
        # fedmsg category names, take their first 3 characters and uppercase
        # them.  That will take things like "wiki" and turn them into "WIK" and
        # "bodhi" and turn them into "BOD".  This handles a lot for us.  We'll
        # then override those that don't make sense manually here.  For
        # instance "fedoratagger" by default would be "FED", but that's no
        # good.  We want "TAG".
        # Why all this trouble?  Well, as new things get added to the fedmsg
        # bus, we don't want to have keep coming back here and modifying this
        # code.  Hopefully this dance will at least partially future-proof us.
        symbols = dict([
            (processor.__name__.lower(), processor.__name__[:3].upper())
            for processor in fedmsg.meta.processors
        ])
        symbols.update({
            'fedoratagger': 'TAG',
            'fedbadges': 'BDG',
            'buildsys': 'KOJ',
            'pkgdb': 'PKG',
            'meetbot': 'MTB',
            'planet': 'PLN',
            'trac': 'TRC',
            'mailman': 'MM3',
        })

        # Now invert the dict so we can lookup the argued symbol.
        # Yes, this is vulnerable to collisions.
        symbols = dict([(sym, name) for name, sym in symbols.items()])

        # These aren't user-facing topics, so drop 'em.
        del symbols['LOG']
        del symbols['UNH']
        del symbols['ANN']  # And this one is unused...

        key_fmt = lambda d: ', '.join(sorted(d.keys()))

        if symbol not in symbols:
            response = "No such symbol %r.  Try one of %s"
            irc.reply((response % (symbol, key_fmt(symbols))).encode('utf-8'))
            return

        # Now, build another lookup of our various timeframes.
        frames = dict(
            daily=datetime.timedelta(days=1),
            weekly=datetime.timedelta(days=7),
            monthly=datetime.timedelta(days=30),
            quarterly=datetime.timedelta(days=91),
        )

        if frame not in frames:
            response = "No such timeframe %r.  Try one of %s"
            irc.reply((response % (frame, key_fmt(frames))).encode('utf-8'))
            return

        category = [symbols[symbol]]

        t2 = datetime.datetime.utcnow()
        t1 = t2 - frames[frame]
        t0 = t1 - frames[frame]

        # Count the number of messages between t0 and t1, and between t1 and t2
        query1 = dict(start=t0, end=t1, category=category)
        query2 = dict(start=t1, end=t2, category=category)

        # Do this async for superfast datagrepper queries.
        tpool = ThreadPool()
        batched_values = tpool.map(datagrepper_query, [
            dict(start=x, end=y, category=category)
            for x, y in Utils.daterange(t1, t2, SPARKLINE_RESOLUTION)
        ] + [query1, query2])

        count2 = batched_values.pop()
        count1 = batched_values.pop()

        # Just rename the results.  We'll use the rest for the sparkline.
        sparkline_values = batched_values

        yester_phrases = dict(
            daily="yesterday",
            weekly="the week preceding this one",
            monthly="the month preceding this one",
            quarterly="the 3 months preceding these past three months",
        )
        phrases = dict(
            daily="24 hours",
            weekly="week",
            monthly="month",
            quarterly="3 months",
        )

        if count1 and count2:
            percent = ((float(count2) / count1) - 1) * 100
        elif not count1 and count2:
            # If the older of the two time periods had zero messages, but there
            # are some in the more current period.. well, that's an infinite
            # percent increase.
            percent = float('inf')
        elif not count1 and not count2:
            # If counts are zero for both periods, then the change is 0%.
            percent = 0
        else:
            # Else, if there were some messages in the old time period, but
            # none in the current... then that's a 100% drop off.
            percent = -100

        sign = lambda value: value >= 0 and '+' or '-'

        template = u"{sym}, {name} {sign}{percent:.2f}% over {phrase}"
        response = template.format(
            sym=symbol,
            name=symbols[symbol],
            sign=sign(percent),
            percent=abs(percent),
            phrase=yester_phrases[frame],
        )
        irc.reply(response.encode('utf-8'))

        # Now, make a graph out of it.
        sparkline = Utils.sparkline(sparkline_values)

        template = u"     {sparkline}  ⤆ over {phrase}"
        response = template.format(
            sym=symbol,
            sparkline=sparkline,
            phrase=phrases[frame]
        )
        irc.reply(response.encode('utf-8'))

        to_utc = lambda t: time.gmtime(time.mktime(t.timetuple()))
        # And a final line for "x-axis tics"
        t1_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t1))
        t2_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t2))
        padding = u" " * (SPARKLINE_RESOLUTION - len(t1_fmt) - 3)
        template = u"     ↑ {t1}{padding}↑ {t2}"
        response = template.format(t1=t1_fmt, t2=t2_fmt, padding=padding)
        irc.reply(response.encode('utf-8'))
    quote = wrap(quote, ['text'])
Esempio n. 32
0
class Fedora(callbacks.Plugin):
    """Use this plugin to retrieve Fedora-related information."""
    threaded = True

    def __init__(self, irc):
        super(Fedora, self).__init__(irc)

        # caches, automatically downloaded on __init__, manually refreshed on
        # .refresh
        self.userlist = None
        self.bugzacl = None

        # To get the information, we need a username and password to FAS.
        # DO NOT COMMIT YOUR USERNAME AND PASSWORD TO THE PUBLIC REPOSITORY!
        self.fasurl = self.registryValue('fas.url')
        self.username = self.registryValue('fas.username')
        self.password = self.registryValue('fas.password')

        self.fasclient = AccountSystem(self.fasurl, username=self.username,
                                       password=self.password)
        self.pkgdb = PkgDB()
        # URLs
        # self.url = {}

        self.github_oauth_token = self.registryValue('github.oauth_token')

        self.karma_tokens = ('++', '--') if self.allow_negative else ('++',)

        # fetch necessary caches
        self._refresh()

        # Pull in /etc/fedmsg.d/ so we can build the fedmsg.meta processors.
        fm_config = fedmsg.config.load_config()
        fedmsg.meta.make_processors(**fm_config)

    def _refresh(self):
        timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(None)
        self.log.info("Downloading user data")
        request = self.fasclient.send_request('/user/list',
                                              req_params={'search': '*'},
                                              auth=True,
                                              timeout=240)
        users = request['people'] + request['unapproved_people']
        del request
        self.log.info("Caching necessary user data")
        self.users = {}
        self.faslist = {}
        self.nickmap = {}
        for user in users:
            name = user['username']
            self.users[name] = {}
            self.users[name]['id'] = user['id']
            key = ' '.join([user['username'], user['email'] or '',
                            user['human_name'] or '', user['ircnick'] or ''])
            key = key.lower()
            value = "%s '%s' <%s>" % (user['username'], user['human_name'] or
                                      '', user['email'] or '')
            self.faslist[key] = value
            if user['ircnick']:
                self.nickmap[user['ircnick']] = name

        self.log.info("Downloading package owners cache")
        data = requests.get(
            'https://admin.fedoraproject.org/pkgdb/api/bugzilla?format=json',
            verify=True).json()
        self.bugzacl = data['bugzillaAcls']
        socket.setdefaulttimeout(timeout)

    def refresh(self, irc, msg, args):
        """takes no arguments

        Refresh the necessary caches."""

        irc.reply("Downloading caches.  This could take a while...")
        self._refresh()
        irc.replySuccess()
    refresh = wrap(refresh)

    @property
    def karma_db_path(self):
        return self.registryValue('karma.db_path')

    @property
    def allow_unaddressed_karma(self):
        return self.registryValue('karma.unaddressed')

    @property
    def allow_negative(self):
        return self.registryValue('karma.allow_negative')

    def _load_json(self, url):
        timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(45)
        json = simplejson.loads(utils.web.getUrl(url))
        socket.setdefaulttimeout(timeout)
        return json

    def pulls(self, irc, msg, args, slug):
        """<username[/repo]>

        List the latest pending pull requests on github/pagure repos.
        """

        slug = slug.strip()
        if not slug or slug.count('/') != 0:
            irc.reply('Must be a GitHub org/username or pagure tag')
            return

        irc.reply('One moment, please...  Looking up %s.' % slug)
        fail_on_github, fail_on_pagure = False, False
        github_repos, pagure_repos = [], []
        try:
            github_repos = list(self.yield_github_repos(slug))
        except IOError as e:
            self.log.exception(e.message)
            fail_on_github = True

        try:
            pagure_repos = list(self.yield_pagure_repos(slug))
        except IOError as e:
            self.log.exception(e.message)
            fail_on_pagure = True

        if fail_on_github and fail_on_pagure:
            irc.reply('Could not find %s on GitHub or pagure.io' % slug)
            return

        results = sum([
            list(self.yield_github_pulls(slug, r)) for r in github_repos
        ], []) + sum([
            list(self.yield_pagure_pulls(slug, r)) for r in pagure_repos
        ], [])

        # Reverse-sort by time (newest-first)
        comparator = lambda a, b: cmp(b['age_numeric'], a['age_numeric'])
        results.sort(comparator)

        if not results:
            irc.reply('No pending pull requests on {slug}'.format(slug=slug))
        else:
            n = 6  # Show 6 pull requests
            for pull in results[:n]:
                irc.reply(u'@{user}\'s "{title}" {url} filed {age}'.format(
                    user=pull['user'],
                    title=pull['title'],
                    url=pull['url'],
                    age=pull['age'],
                ).encode('utf-8'))

            if len(results) > n:
                irc.reply('... and %i more.' % (len(results) - n))
    pulls = wrap(pulls, ['text'])

    def yield_github_repos(self, username):
        self.log.info("Finding github repos for %r" % username)
        tmpl = "https://api.github.com/users/{username}/repos?per_page=100"
        url = tmpl.format(username=username)
        auth = dict(access_token=self.github_oauth_token)
        for result in self.yield_github_results(url, auth):
            yield result['name']

    def yield_github_pulls(self, username, repo):
        self.log.info("Finding github pull requests for %r %r" % (username, repo))
        tmpl = "https://api.github.com/repos/{username}/{repo}/" + \
            "pulls?per_page=100"
        url = tmpl.format(username=username, repo=repo)
        auth = dict(access_token=self.github_oauth_token)
        for result in self.yield_github_results(url, auth):
            yield dict(
                user=result['user']['login'],
                title=result['title'],
                url=result['html_url'],
                age=arrow.get(result['created_at']).humanize(),
                age_numeric=arrow.get(result['created_at']),
            )

    def yield_github_results(self, url, auth):
        results = []
        link = dict(next=url)
        while 'next' in link:
            response = requests.get(link['next'], params=auth)

            if response.status_code == 404:
                raise IOError("404 for %r" % link['next'])

            # And.. if we didn't get good results, just bail.
            if response.status_code != 200:
                raise IOError(
                    "Non-200 status code %r; %r; %r" % (
                        response.status_code, link['next'], response.json))

            results = response.json()

            for result in results:
                yield result

            field = response.headers.get('link', None)

            link = dict()
            if field:
                link = dict([
                    (
                        part.split('; ')[1][5:-1],
                        part.split('; ')[0][1:-1],
                    ) for part in field.split(', ')
                ])

    def yield_pagure_repos(self, tag):
        self.log.info("Finding pagure repos for %r" % tag)
        tmpl = "https://pagure.io/api/0/projects?tags={tag}"
        url = tmpl.format(tag=tag)
        for result in self.yield_pagure_results(url, 'projects'):
            yield result['name']

    def yield_pagure_pulls(self, tag, repo):
        self.log.info("Finding pagure pull requests for %r %r" % (tag, repo))
        tmpl = "https://pagure.io/api/0/{repo}/pull-requests"
        url = tmpl.format(tag=tag, repo=repo)
        for result in self.yield_pagure_results(url, 'requests'):
            yield dict(
                user=result['user']['name'],
                title=result['title'],
                url='https://pagure.io/{repo}/pull-request/{id}'.format(
                    repo=result['project']['name'], id=result['id']),
                age=arrow.get(result['date_created']).humanize(),
                age_numeric=arrow.get(result['date_created']),
            )

    def yield_pagure_results(self, url, key):
        response = requests.get(url)

        if response.status_code == 404:
            raise IOError("404 for %r" % url)

        # And.. if we didn't get good results, just bail.
        if response.status_code != 200:
            raise IOError(
                "Non-200 status code %r; %r; %r" % (
                    response.status_code, url, response.text))

        results = response.json()
        results = results[key]

        for result in results:
            yield result

    def whoowns(self, irc, msg, args, package):
        """<package>

        Retrieve the owner of a given package
        """
        try:
            mainowner = self.bugzacl['Fedora'][package]['owner']
        except KeyError:
            irc.reply("No such package exists.")
            return
        others = []
        for key in self.bugzacl:
            if key == 'Fedora':
                continue
            try:
                owner = self.bugzacl[key][package]['owner']
                if owner == mainowner:
                    continue
            except KeyError:
                continue
            others.append("%s in %s" % (owner, key))
        if others == []:
            irc.reply(mainowner)
        else:
            irc.reply("%s (%s)" % (mainowner, ', '.join(others)))
    whoowns = wrap(whoowns, ['text'])

    def branches(self, irc, msg, args, package):
        """<package>

        Return the branches a package is in."""
        try:
            pkginfo = self.pkgdb.get_package(package)
        except AppError:
            irc.reply("No such package exists.")
            return
        branch_list = []
        for listing in pkginfo['packages']:
            branch_list.append(listing['collection']['branchname'])
        branch_list.sort()
        irc.reply(' '.join(branch_list))
        return
    branches = wrap(branches, ['text'])

    def what(self, irc, msg, args, package):
        """<package>

        Returns a description of a given package.
        """
        try:
            summary = self.bugzacl['Fedora'][package]['summary']
            irc.reply("%s: %s" % (package, summary))
        except KeyError:
            irc.reply("No such package exists.")
            return
    what = wrap(what, ['text'])

    def fas(self, irc, msg, args, find_name):
        """<query>

        Search the Fedora Account System usernames, full names, and email
        addresses for a match."""
        find_name = to_unicode(find_name)
        matches = []
        for entry in self.faslist.keys():
            if entry.find(find_name.lower()) != -1:
                matches.append(entry)
        if len(matches) == 0:
            irc.reply("'%s' Not Found!" % find_name)
        else:
            output = []
            for match in matches:
                output.append(self.faslist[match])
            irc.reply(' - '.join(output).encode('utf-8'))
    fas = wrap(fas, ['text'])

    def hellomynameis(self, irc, msg, args, name):
        """<username>

        Return brief information about a Fedora Account System username. Useful
        for things like meeting roll call and calling attention to yourself."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Something blew up, please try again')
            return
        if not person:
            irc.reply('Sorry, but you don\'t exist')
            return
        irc.reply(('%(username)s \'%(human_name)s\' <%(email)s>' %
                   person).encode('utf-8'))
    hellomynameis = wrap(hellomynameis, ['text'])

    def himynameis(self, irc, msg, args, name):
        """<username>

        Will the real Slim Shady please stand up?"""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Something blew up, please try again')
            return
        if not person:
            irc.reply('Sorry, but you don\'t exist')
            return
        irc.reply(('%(username)s \'Slim Shady\' <%(email)s>' %
                   person).encode('utf-8'))
    himynameis = wrap(himynameis, ['text'])

    def dctime(self, irc, msg, args, dcname):
        """<dcname>

        Returns the current time of the datacenter identified by dcname.
        Supported DCs: PHX2, RDU, AMS, osuosl, ibiblio."""
        timezone_name = ''
        dcname_lower = dcname.lower()
        if dcname_lower == 'phx2':
            timezone_name = 'US/Arizona'
        elif dcname_lower in ['rdu', 'ibiblio']:
            timezone_name = 'US/Eastern'
        elif dcname_lower == 'osuosl':
            timezone_name = 'US/Pacific'
        elif dcname_lower in ['ams', 'internetx']:
            timezone_name = 'Europe/Amsterdam'
        else:
            irc.reply('Datacenter %s is unknown' % dcname)
            return
        try:
            time = datetime.datetime.now(pytz.timezone(timezone_name))
        except:
            irc.reply('The timezone of "%s" was unknown: "%s"' % (
                dcname, timezone_name))
            return
        irc.reply('The current local time of "%s" is: "%s" (timezone: %s)' %
                  (dcname, time.strftime('%H:%M'), timezone_name))
    dctime = wrap(dctime, ['text'])

    def localtime(self, irc, msg, args, name):
        """<username>

        Returns the current time of the user.
        The timezone is queried from FAS."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info user user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        timezone_name = person['timezone']
        if timezone_name is None:
            irc.reply('User "%s" doesn\'t share his timezone' % name)
            return
        try:
            time = datetime.datetime.now(pytz.timezone(timezone_name))
        except:
            irc.reply('The timezone of "%s" was unknown: "%s"' % (
                name, timezone_name))
            return
        irc.reply('The current local time of "%s" is: "%s" (timezone: %s)' %
                  (name, time.strftime('%H:%M'), timezone_name))
    localtime = wrap(localtime, ['text'])

    def fasinfo(self, irc, msg, args, name):
        """<username>

        Return information on a Fedora Account System username."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info for user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        person['creation'] = person['creation'].split(' ')[0]
        string = ("User: %(username)s, Name: %(human_name)s"
                  ", email: %(email)s, Creation: %(creation)s"
                  ", IRC Nick: %(ircnick)s, Timezone: %(timezone)s"
                  ", Locale: %(locale)s"
                  ", GPG key ID: %(gpg_keyid)s, Status: %(status)s") % person
        irc.reply(string.encode('utf-8'))

        # List of unapproved groups is easy
        unapproved = ''
        for group in person['unapproved_memberships']:
            unapproved = unapproved + "%s " % group['name']
        if unapproved != '':
            irc.reply('Unapproved Groups: %s' % unapproved)

        # List of approved groups requires a separate query to extract roles
        constraints = {'username': name, 'group': '%',
                       'role_status': 'approved'}
        columns = ['username', 'group', 'role_type']
        roles = []
        try:
            roles = self.fasclient.people_query(constraints=constraints,
                                                columns=columns)
        except:
            irc.reply('Error getting group memberships.')
            return

        approved = ''
        for role in roles:
            if role['role_type'] == 'sponsor':
                approved += '+' + role['group'] + ' '
            elif role['role_type'] == 'administrator':
                approved += '@' + role['group'] + ' '
            else:
                approved += role['group'] + ' '
        if approved == '':
            approved = "None"

        irc.reply('Approved Groups: %s' % approved)
    fasinfo = wrap(fasinfo, ['text'])

    def group(self, irc, msg, args, name):
        """<group short name>

        Return information about a Fedora Account System group."""
        try:
            group = self.fasclient.group_by_name(name)
            irc.reply('%s: %s' %
                      (name, group['display_name']))
        except AppError:
            irc.reply('There is no group "%s".' % name)
    group = wrap(group, ['text'])

    def admins(self, irc, msg, args, name):
        """<group short name>

        Return the administrators list for the selected group"""

        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    sponsors += person['username'] + ' '
            irc.reply('Administrators for %s: %s' % (name, sponsors))
        except AppError:
            irc.reply('There is no group %s.' % name)

    admins = wrap(admins, ['text'])

    def sponsors(self, irc, msg, args, name):
        """<group short name>

        Return the sponsors list for the selected group"""

        try:
            group = self.fasclient.group_members(name)
            sponsors = ''
            for person in group:
                if person['role_type'] == 'sponsor':
                    sponsors += person['username'] + ' '
                elif person['role_type'] == 'administrator':
                    sponsors += '@' + person['username'] + ' '
            irc.reply('Sponsors for %s: %s' % (name, sponsors))
        except AppError:
            irc.reply('There is no group %s.' % name)

    sponsors = wrap(sponsors, ['text'])

    def members(self, irc, msg, args, name):
        """<group short name>

        Return a list of members of the specified group"""
        try:
            group = self.fasclient.group_members(name)
            members = ''
            for person in group:
                if person['role_type'] == 'administrator':
                    members += '@' + person['username'] + ' '
                elif person['role_type'] == 'sponsor':
                    members += '+' + person['username'] + ' '
                else:
                    members += person['username'] + ' '
            irc.reply('Members of %s: %s' % (name, members))
        except AppError:
            irc.reply('There is no group %s.' % name)

    members = wrap(members, ['text'])

    def showticket(self, irc, msg, args, baseurl, number):
        """<baseurl> <number>

        Return the name and URL of a trac ticket or bugzilla bug.
        """
        url = format(baseurl, str(number))
        size = conf.supybot.protocols.http.peekSize()
        text = utils.web.getUrl(url, size=size)
        parser = Title()
        try:
            parser.feed(text)
        except sgmllib.SGMLParseError:
            irc.reply(format('Encountered a problem parsing %u', url))
        if parser.title:
            irc.reply(utils.web.htmlToText(parser.title.strip()) + ' - ' + url)
        else:
            irc.reply(format('That URL appears to have no HTML title ' +
                             'within the first %i bytes.', size))
    showticket = wrap(showticket, ['httpUrl', 'int'])

    def swedish(self, irc, msg, args):
        """takes no arguments

        Humor mmcgrath."""

        # Import this here to avoid a circular import problem.
        from __init__ import __version__

        irc.reply(str('kwack kwack'))
        irc.reply(str('bork bork bork'))
        irc.reply(str('(supybot-fedora version %s)' % __version__))
    swedish = wrap(swedish)

    def invalidCommand(self, irc, msg, tokens):
        """ Handle any command not otherwise handled.

        We use this to accept karma commands directly.
        """
        channel = msg.args[0]
        if not irc.isChannel(channel):
            return

        agent = msg.nick
        line = tokens[-1].strip()
        words = line.split()
        for word in words:
            if word[-2:] in self.karma_tokens:
                self._do_karma(irc, channel, agent, word, line, explicit=True)

    def doPrivmsg(self, irc, msg):
        """ Handle everything.

        The name is misleading.  This hook actually gets called for all
        IRC activity in every channel.
        """
        # We don't handle this if we've been addressed because invalidCommand
        # will handle it for us.  This prevents us from accessing the db twice
        # and therefore crashing.
        if (msg.addressed or msg.repliedTo):
            return

        channel = msg.args[0]
        if irc.isChannel(channel) and self.allow_unaddressed_karma:
            irc = callbacks.SimpleProxy(irc, msg)
            agent = msg.nick
            line = msg.args[1].strip()

            # First try to handle karma commands
            words = line.split()
            for word in words:
                if word[-2:] in self.karma_tokens:
                    self._do_karma(
                        irc, channel, agent, word, line, explicit=False)

        blacklist = self.registryValue('naked_ping_channel_blacklist')
        if irc.isChannel(channel) and not channel in blacklist:
            # Also, handle naked pings for
            # https://github.com/fedora-infra/supybot-fedora/issues/26
            pattern = '\w* ?[:,] ?ping\W*$'
            if re.match(pattern, line):
                admonition = self.registryValue('naked_ping_admonition')
                irc.reply(admonition)

    def get_current_release(self):
        url = 'https://admin.fedoraproject.org/pkgdb/api/collections/'
        query = {
            'clt_status': 'Active',
            'pattern': 'f*',
        }
        response = requests.get(url, params=query)
        data = response.json()
        collections = data['collections']
        collections.sort(key=lambda c: int(c['version']))
        return collections[-1]['branchname'].encode('utf-8')

    def open_karma_db(self):
        data = shelve.open(self.karma_db_path)
        if 'backwards' in data:
            # This is the old style data.  convert it to the new form.
            release = self.get_current_release()
            data['forwards-' + release] = copy.copy(data['forwards'])
            data['backwards-' + release] = copy.copy(data['backwards'])
            del data['forwards']
            del data['backwards']
            data.sync()
        return data

    def karma(self, irc, msg, args, name):
        """<username>

        Return the total karma for a FAS user."""
        data = None
        try:
            data = self.open_karma_db()
            if name in self.nickmap:
                name = self.nickmap[name]
            release = self.get_current_release()
            votes = data['backwards-' + release].get(name, {})
            alltime = []
            for key in data:
                if 'backwards-' not in key:
                    continue
                alltime.append(data[key].get(name, {}))
        finally:
            if data:
                data.close()

        inc = len([v for v in votes.values() if v == 1])
        dec = len([v for v in votes.values() if v == -1])
        total = inc - dec

        inc, dec = 0, 0
        for release in alltime:
            inc += len([v for v in release.values() if v == 1])
            dec += len([v for v in release.values() if v == -1])
        alltime_total = inc - dec

        irc.reply("Karma for %s has been increased %i times and "
                    "decreased %i times this release cycle for a "
                    "total of %i (%i all time)" % (
                    name, inc, dec, total, alltime_total))


    karma = wrap(karma, ['text'])

    def _do_karma(self, irc, channel, agent, recip, line, explicit=False):
        recip, direction = recip[:-2], recip[-2:]
        if not recip:
            return

        # Extract 'puiterwijk' out of 'have a cookie puiterwijk++'
        recip = recip.strip().split()[-1]

        # Exclude 'c++', 'g++' or 'i++' (c,g,i), issue #30
        if str(recip).lower() in ['c','g','i']:
            return

        increment = direction == '++' # If not, then it must be decrement

        # Check that these are FAS users
        if not agent in self.nickmap and not agent in self.users:
            self.log.info(
                "Saw %s from %s, but %s not in FAS" % (recip, agent, agent))
            if explicit:
                irc.reply("Couldn't find %s in FAS" % agent)
            return

        if not recip in self.nickmap and not recip in self.users:
            self.log.info(
                "Saw %s from %s, but %s not in FAS" % (recip, agent, recip))
            if explicit:
                irc.reply("Couldn't find %s in FAS" % recip)
            return

        # Transform irc nicks into fas usernames if possible.
        if agent in self.nickmap:
            agent = self.nickmap[agent]

        if recip in self.nickmap:
            recip = self.nickmap[recip]

        if agent == recip:
            irc.reply("You may not modify your own karma.")
            return

        release = self.get_current_release()

        # Check our karma db to make sure this hasn't already been done.
        data = None
        try:
            data = shelve.open(self.karma_db_path)
            fkey = 'forwards-' + release
            bkey = 'backwards-' + release
            if fkey not in data:
                data[fkey] = {}

            if bkey not in data:
                data[bkey] = {}

            if agent not in data[fkey]:
                forwards = data[fkey]
                forwards[agent] = {}
                data[fkey] = forwards

            if recip not in data[bkey]:
                backwards = data[bkey]
                backwards[recip] = {}
                data[bkey] = backwards

            vote = 1 if increment else -1

            if data[fkey][agent].get(recip) == vote:
                ## People found this response annoying.
                ## https://github.com/fedora-infra/supybot-fedora/issues/25
                #irc.reply(
                #    "You have already given %i karma to %s" % (vote, recip))
                return

            forwards = data[fkey]
            forwards[agent][recip] = vote
            data[fkey] = forwards

            backwards = data[bkey]
            backwards[recip][agent] = vote
            data[bkey] = backwards

            # Count the number of karmas for old so-and-so.
            total_this_release = sum(data[bkey][recip].values())

            total_all_time = 0
            for key in data:
                if 'backwards-' not in key:
                    continue
                total_all_time += sum(data[key].get(recip, {}).values())
        finally:
            if data:
                data.close()

        fedmsg.publish(
            name="supybot.%s" % socket.gethostname(),
            modname="irc", topic="karma",
            msg={
                'agent': agent,
                'recipient': recip,
                'total': total_all_time,  # The badge rules use this value
                'total_this_release': total_this_release,
                'vote': vote,
                'channel': channel,
                'line': line,
                'release': release,
            },
        )

        url = self.registryValue('karma.url')
        irc.reply(
            'Karma for %s changed to %r '
            '(for the %s release cycle):  %s' % (
                recip, total_this_release, release, url))


    def wikilink(self, irc, msg, args, name):
        """<username>

        Return MediaWiki link syntax for a FAS user's page on the wiki."""
        try:
            person = self.fasclient.person_by_username(name)
        except:
            irc.reply('Error getting info for user: "******"' % name)
            return
        if not person:
            irc.reply('User "%s" doesn\'t exist' % name)
            return
        string = "[[User:%s|%s]]" % (person["username"],
                                     person["human_name"] or '')
        irc.reply(string.encode('utf-8'))
    wikilink = wrap(wikilink, ['text'])

    def mirroradmins(self, irc, msg, args, hostname):
        """<hostname>

        Return MirrorManager list of FAS usernames which administer <hostname>.
        <hostname> must be the FQDN of the host."""
        url = ("https://admin.fedoraproject.org/mirrormanager/api/"
               "mirroradmins?name=" + hostname)
        result = self._load_json(url)
        if not 'admins' in result:
            irc.reply(result.get('message', 'Something went wrong'))
            return
        string = 'Mirror Admins of %s: ' % hostname
        string += ' '.join(result['admins'])
        irc.reply(string.encode('utf-8'))
    mirroradmins = wrap(mirroradmins, ['text'])

    def pushduty(self, irc, msg, args):
        """

        Return the list of people who are on releng push duty right now.
        """

        def get_persons():
            for meeting in self._meetings_for('release-engineering'):
                yield meeting['meeting_name']

        persons = list(get_persons())

        url = "https://apps.fedoraproject.org/" + \
            "calendar/release-engineering/"

        if not persons:
            response = "Nobody is listed as being on push duty right now..."
            irc.reply(response.encode('utf-8'))
            irc.reply("- " + url.encode('utf-8'))
            return

        persons = ", ".join(persons)
        response = "The following people are on push duty: %s" % persons
        irc.reply(response.encode('utf-8'))
        irc.reply("- " + url.encode('utf-8'))
    pushduty = wrap(pushduty)

    def vacation(self, irc, msg, args):
        """

        Return the list of people who are on vacation right now.
        """

        def get_persons():
            for meeting in self._meetings_for('vacation'):
                for manager in meeting['meeting_manager']:
                    yield manager

        persons = list(get_persons())

        if not persons:
            response = "Nobody is listed as being on vacation right now..."
            irc.reply(response.encode('utf-8'))
            url = "https://apps.fedoraproject.org/calendar/vacation/"
            irc.reply("- " + url.encode('utf-8'))
            return

        persons = ", ".join(persons)
        response = "The following people are on vacation: %s" % persons
        irc.reply(response.encode('utf-8'))
        url = "https://apps.fedoraproject.org/calendar/vacation/"
        irc.reply("- " + url.encode('utf-8'))
    vacation = wrap(vacation)

    def nextmeetings(self, irc, msg, args):
        """
        Return the next meetings scheduled for any channel(s).
        """
        irc.reply('One moment, please...  Looking up the channel list.')
        url = 'https://apps.fedoraproject.org/calendar/api/locations/'
        locations = requests.get(url).json()['locations']
        meetings = sorted(chain(*[
            self._future_meetings(location)
            for location in locations
            if 'irc.freenode.net' in location
        ]), key=itemgetter(0))

        test, meetings = tee(meetings)
        try:
            test.next()
        except StopIteration:
            response = "There are no meetings scheduled at all."
            irc.reply(response.encode('utf-8'))
            return

        for date, meeting in islice(meetings, 0, 5):
            response = "In #%s is %s (starting %s)" % (
                meeting['meeting_location'].split('@')[0].strip(),
                meeting['meeting_name'],
                arrow.get(date).humanize(),
            )
            irc.reply(response.encode('utf-8'))
    nextmeetings = wrap(nextmeetings, [])

    def nextmeeting(self, irc, msg, args, channel):
        """<channel>

        Return the next meeting scheduled for a particular channel.
        """

        channel = channel.strip('#').split('@')[0]
        meetings = sorted(self._future_meetings(channel), key=itemgetter(0))

        test, meetings = tee(meetings)
        try:
            test.next()
        except StopIteration:
            response = "There are no meetings scheduled for #%s." % channel
            irc.reply(response.encode('utf-8'))
            return

        for date, meeting in islice(meetings, 0, 3):
            response = "In #%s is %s (starting %s)" % (
                channel,
                meeting['meeting_name'],
                arrow.get(date).humanize(),
            )
            irc.reply(response.encode('utf-8'))
        base = "https://apps.fedoraproject.org/calendar/location/"
        url = base + urllib.quote("%[email protected]/" % channel)
        irc.reply("- " + url.encode('utf-8'))
    nextmeeting = wrap(nextmeeting, ['text'])

    @staticmethod
    def _future_meetings(location):
        if not location.endswith('@irc.freenode.net'):
            location = '*****@*****.**' % location
        meetings = Fedora._query_fedocal(location=location)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            dt = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now < dt:
                yield dt, meeting

    @staticmethod
    def _meetings_for(calendar):
        meetings = Fedora._query_fedocal(calendar=calendar)
        now = datetime.datetime.utcnow()

        for meeting in meetings:
            string = "%s %s" % (meeting['meeting_date'],
                                meeting['meeting_time_start'])
            start = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
            string = "%s %s" % (meeting['meeting_date_end'],
                                meeting['meeting_time_stop'])
            end = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")

            if now >= start and now <= end:
                yield meeting

    @staticmethod
    def _query_fedocal(**kwargs):
        url = 'https://apps.fedoraproject.org/calendar/api/meetings'
        return requests.get(url, params=kwargs).json()['meetings']

    def badges(self, irc, msg, args, name):
        """<username>

        Return badges statistics about a user.
        """
        url = "https://badges.fedoraproject.org/user/" + name
        d = requests.get(url + "/json").json()

        if 'error' in d:
            response = d['error']
        else:
            template = "{name} has unlocked {n} Fedora Badges:  {url}"
            n = len(d['assertions'])
            response = template.format(name=name, url=url, n=n)

        irc.reply(response.encode('utf-8'))
    badges = wrap(badges, ['text'])

    def quote(self, irc, msg, args, arguments):
        """<SYMBOL> [daily, weekly, monthly, quarterly]

        Return some datagrepper statistics on fedmsg categories.
        """

        # First, some argument parsing.  Supybot should be able to do this for
        # us, but I couldn't figure it out.  The supybot.plugins.additional
        # object is the thing to use... except its weird.
        tokens = arguments.split(None, 1)
        if len(tokens) == 1:
            symbol, frame = tokens[0], 'daily'
        else:
            symbol, frame = tokens

        # Second, build a lookup table for symbols.  By default, we'll use the
        # fedmsg category names, take their first 3 characters and uppercase
        # them.  That will take things like "wiki" and turn them into "WIK" and
        # "bodhi" and turn them into "BOD".  This handles a lot for us.  We'll
        # then override those that don't make sense manually here.  For
        # instance "fedoratagger" by default would be "FED", but that's no
        # good.  We want "TAG".
        # Why all this trouble?  Well, as new things get added to the fedmsg
        # bus, we don't want to have keep coming back here and modifying this
        # code.  Hopefully this dance will at least partially future-proof us.
        symbols = dict([
            (processor.__name__.lower(), processor.__name__[:3].upper())
            for processor in fedmsg.meta.processors
        ])
        symbols.update({
            'fedoratagger': 'TAG',
            'fedbadges': 'BDG',
            'buildsys': 'KOJ',
            'pkgdb': 'PKG',
            'meetbot': 'MTB',
            'planet': 'PLN',
            'trac': 'TRC',
            'mailman': 'MM3',
        })

        # Now invert the dict so we can lookup the argued symbol.
        # Yes, this is vulnerable to collisions.
        symbols = dict([(sym, name) for name, sym in symbols.items()])

        # These aren't user-facing topics, so drop 'em.
        del symbols['LOG']
        del symbols['UNH']
        del symbols['ANN']  # And this one is unused...

        key_fmt = lambda d: ', '.join(sorted(d.keys()))

        if symbol not in symbols:
            response = "No such symbol %r.  Try one of %s"
            irc.reply((response % (symbol, key_fmt(symbols))).encode('utf-8'))
            return

        # Now, build another lookup of our various timeframes.
        frames = dict(
            daily=datetime.timedelta(days=1),
            weekly=datetime.timedelta(days=7),
            monthly=datetime.timedelta(days=30),
            quarterly=datetime.timedelta(days=91),
        )

        if frame not in frames:
            response = "No such timeframe %r.  Try one of %s"
            irc.reply((response % (frame, key_fmt(frames))).encode('utf-8'))
            return

        category = [symbols[symbol]]

        t2 = datetime.datetime.utcnow()
        t1 = t2 - frames[frame]
        t0 = t1 - frames[frame]

        # Count the number of messages between t0 and t1, and between t1 and t2
        query1 = dict(start=t0, end=t1, category=category)
        query2 = dict(start=t1, end=t2, category=category)

        # Do this async for superfast datagrepper queries.
        tpool = ThreadPool()
        batched_values = tpool.map(datagrepper_query, [
            dict(start=x, end=y, category=category)
            for x, y in Utils.daterange(t1, t2, SPARKLINE_RESOLUTION)
        ] + [query1, query2])

        count2 = batched_values.pop()
        count1 = batched_values.pop()

        # Just rename the results.  We'll use the rest for the sparkline.
        sparkline_values = batched_values

        yester_phrases = dict(
            daily="yesterday",
            weekly="the week preceding this one",
            monthly="the month preceding this one",
            quarterly="the 3 months preceding these past three months",
        )
        phrases = dict(
            daily="24 hours",
            weekly="week",
            monthly="month",
            quarterly="3 months",
        )

        if count1 and count2:
            percent = ((float(count2) / count1) - 1) * 100
        elif not count1 and count2:
            # If the older of the two time periods had zero messages, but there
            # are some in the more current period.. well, that's an infinite
            # percent increase.
            percent = float('inf')
        elif not count1 and not count2:
            # If counts are zero for both periods, then the change is 0%.
            percent = 0
        else:
            # Else, if there were some messages in the old time period, but
            # none in the current... then that's a 100% drop off.
            percent = -100

        sign = lambda value: value >= 0 and '+' or '-'

        template = u"{sym}, {name} {sign}{percent:.2f}% over {phrase}"
        response = template.format(
            sym=symbol,
            name=symbols[symbol],
            sign=sign(percent),
            percent=abs(percent),
            phrase=yester_phrases[frame],
        )
        irc.reply(response.encode('utf-8'))

        # Now, make a graph out of it.
        sparkline = Utils.sparkline(sparkline_values)

        template = u"     {sparkline}  ⤆ over {phrase}"
        response = template.format(
            sym=symbol,
            sparkline=sparkline,
            phrase=phrases[frame]
        )
        irc.reply(response.encode('utf-8'))

        to_utc = lambda t: time.gmtime(time.mktime(t.timetuple()))
        # And a final line for "x-axis tics"
        t1_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t1))
        t2_fmt = time.strftime("%H:%M UTC %m/%d", to_utc(t2))
        padding = u" " * (SPARKLINE_RESOLUTION - len(t1_fmt) - 3)
        template = u"     ↑ {t1}{padding}↑ {t2}"
        response = template.format(t1=t1_fmt, t2=t2_fmt, padding=padding)
        irc.reply(response.encode('utf-8'))
    quote = wrap(quote, ['text'])
Esempio n. 33
0
from fedora.client import (AppError, ServerError)

import argparse
import requests
import logging
import koji
import itertools

from pkgdb2client import PkgDB, PkgDBException, __version__
import pkgdb2client
import pkgdb2client.utils

KOJI_HUB = 'http://koji.fedoraproject.org/kojihub'

pkgdbclient = PkgDB('https://admin.fedoraproject.org/pkgdb',
                    login_callback=pkgdb2client.ask_password)
BOLD = "\033[1m"
RED = "\033[0;31m"
RESET = "\033[0;0m"

# Initial simple logging stuff
logging.basicConfig()
PKGDBLOG = logging.getLogger("pkgdb2client")
LOG = logging.getLogger("pkgdb-cli")

ACTIONLIST = ['watchbugzilla', 'watchcommits', 'commit', 'approveacls']


class ActionError(Exception):
    ''' This class is raised when an ACL action is requested but not in
    the list of allowed action. '''
Esempio n. 34
0
"""

from fedora.client import (AppError, ServerError)

import argparse
import requests
import logging
import koji

from pkgdb2client import PkgDB, PkgDBException, __version__
from pkgdb2client.cli import ActionError
import pkgdb2client
import pkgdb2client.utils as utils


PKGDBCLIENT = PkgDB('https://admin.fedoraproject.org/pkgdb',
                    login_callback=pkgdb2client.ask_password)
BOLD = "\033[1m"
RED = "\033[0;31m"
RESET = "\033[0;0m"

# Initial simple logging stuff
logging.basicConfig()
PKGDBLOG = logging.getLogger("pkgdb2client")
LOG = logging.getLogger("pkgdb-admin")


def setup_parser():
    '''
    Set the main arguments.
    '''
    parser = argparse.ArgumentParser(prog="pkgdb-admin")