Merge branch 'master' of /git/ansible
This commit is contained in:
commit
a3fbc0f38d
8 changed files with 696 additions and 4 deletions
|
@ -22,7 +22,7 @@
|
||||||
# Note that, we're cool with arbitrary restarts on bodhi-backend02, just
|
# Note that, we're cool with arbitrary restarts on bodhi-backend02, just
|
||||||
# not bodhi-backend01 or bodhi-backend03. 01 and 03 is where the releng/mash
|
# not bodhi-backend01 or bodhi-backend03. 01 and 03 is where the releng/mash
|
||||||
# stuff happens and we # don't want to interrupt that.
|
# stuff happens and we # don't want to interrupt that.
|
||||||
when: inventory_hostname == 'bodhi-backend02.phx2.fedoraproject.org'
|
when: inventory_hostname not in ['bodhi-backend01.phx2.fedoraproject.org', 'bodhi-backend03.phx2.fedoraproject.org']
|
||||||
|
|
||||||
- name: restart fedmsg-irc
|
- name: restart fedmsg-irc
|
||||||
command: /usr/local/bin/conditional-restart.sh fedmsg-irc fedmsg-irc
|
command: /usr/local/bin/conditional-restart.sh fedmsg-irc fedmsg-irc
|
||||||
|
|
|
@ -53,7 +53,7 @@ resultsdb_url: http://resultsdb-stg01.qa.fedoraproject.org/resultsdb_api/api/v2.
|
||||||
taskotron_docs_url: https://qa.fedoraproject.org/docs/libtaskotron/latest/
|
taskotron_docs_url: https://qa.fedoraproject.org/docs/libtaskotron/latest/
|
||||||
# make sure this doesn't have a trailing slash
|
# make sure this doesn't have a trailing slash
|
||||||
trigger_distgit_repo_url: git://pkgs02.phx2.fedoraproject.org
|
trigger_distgit_repo_url: git://pkgs02.phx2.fedoraproject.org
|
||||||
trigger_critpath_url: https://admin.fedoraproject.org/pkgdb/api/critpath?format=json
|
trigger_critpath_url: https://admin.stg.fedoraproject.org/pkgdb/api/critpath?format=json
|
||||||
|
|
||||||
instances:
|
instances:
|
||||||
- { name: "Production", url: "https://taskotron.fedoraproject.org"}
|
- { name: "Production", url: "https://taskotron.fedoraproject.org"}
|
||||||
|
|
|
@ -14,6 +14,11 @@
|
||||||
- libsemanage-python
|
- libsemanage-python
|
||||||
- python-fedora-flask
|
- python-fedora-flask
|
||||||
- python2-pagure-dist-git
|
- python2-pagure-dist-git
|
||||||
|
# For the pagure-sync-bugzilla.py script
|
||||||
|
- python-bugzilla
|
||||||
|
- python-requests
|
||||||
|
- PyYAML
|
||||||
|
- python-six
|
||||||
# - mod_ssl
|
# - mod_ssl
|
||||||
# - stunnel
|
# - stunnel
|
||||||
tags:
|
tags:
|
||||||
|
@ -171,6 +176,29 @@
|
||||||
- pagure
|
- pagure
|
||||||
|
|
||||||
|
|
||||||
|
- name: generate pagure-sync-bugzilla.py script
|
||||||
|
template:
|
||||||
|
src: pagure-sync-bugzilla.py.j2
|
||||||
|
dest: /usr/local/bin/pagure-sync-bugzilla.py
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: 0700
|
||||||
|
tags:
|
||||||
|
- pagure
|
||||||
|
|
||||||
|
|
||||||
|
- name: Configure cron job for a daily pagure-sync-bugzilla.py script run
|
||||||
|
cron:
|
||||||
|
name: pagure-sync-bugzilla
|
||||||
|
user: root
|
||||||
|
minute: 0
|
||||||
|
hour: 18
|
||||||
|
job: /usr/local/bin/pagure-sync-bugzilla
|
||||||
|
cron_file: pagure-sync-bugzilla
|
||||||
|
state: present
|
||||||
|
tags:
|
||||||
|
- pagure
|
||||||
|
|
||||||
# Ensure all the services are up and running
|
# Ensure all the services are up and running
|
||||||
|
|
||||||
- name: Start and enable httpd, postfix, pagure_milter
|
- name: Start and enable httpd, postfix, pagure_milter
|
||||||
|
|
648
roles/distgit/pagure/templates/pagure-sync-bugzilla.py.j2
Normal file
648
roles/distgit/pagure/templates/pagure-sync-bugzilla.py.j2
Normal file
|
@ -0,0 +1,648 @@
|
||||||
|
#!/usr/bin/python -tt
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright © 2013-2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This copyrighted material is made available to anyone wishing to use, modify,
|
||||||
|
# copy, or redistribute it subject to the terms and conditions of the GNU
|
||||||
|
# General Public License v.2, or (at your option) any later version. This
|
||||||
|
# program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY expressed or implied, including the implied warranties of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details. You should have received a copy of the GNU
|
||||||
|
# General Public License along with this program; if not, write to the Free
|
||||||
|
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the source
|
||||||
|
# code or documentation are not subject to the GNU General Public License and
|
||||||
|
# may only be used or replicated with the express permission of Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Red Hat Author(s): Toshio Kuratomi <tkuratom@redhat.com>
|
||||||
|
# Author(s): Mike Watters <valholla75@fedoraproject.org>
|
||||||
|
# Author(s): Pierre-Yves Chibon <pingou@pingoured.fr>
|
||||||
|
# Author(s): Matt Prahl <mprahl@redhat.com>
|
||||||
|
#
|
||||||
|
'''
|
||||||
|
sync information from the Pagure into bugzilla
|
||||||
|
|
||||||
|
This short script takes information about package onwership and imports it
|
||||||
|
into bugzilla.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import itertools
|
||||||
|
import json
|
||||||
|
import xmlrpclib
|
||||||
|
import codecs
|
||||||
|
import smtplib
|
||||||
|
try:
|
||||||
|
from email.Message import Message
|
||||||
|
except ImportError:
|
||||||
|
from email.message import EmailMessage as Message
|
||||||
|
|
||||||
|
import bugzilla
|
||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
from six import string_types
|
||||||
|
from fedora.client.fas2 import AccountSystem
|
||||||
|
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.packages.urllib3.util.retry import Retry
|
||||||
|
|
||||||
|
|
||||||
|
def retry_session():
|
||||||
|
session = requests.Session()
|
||||||
|
retry = Retry(
|
||||||
|
total=5,
|
||||||
|
read=5,
|
||||||
|
connect=5,
|
||||||
|
backoff_factor=0.3,
|
||||||
|
status_forcelist=(500, 502, 504),
|
||||||
|
)
|
||||||
|
adapter = HTTPAdapter(max_retries=retry)
|
||||||
|
session.mount('http://', adapter)
|
||||||
|
session.mount('https://', adapter)
|
||||||
|
return session
|
||||||
|
|
||||||
|
BZSERVER = 'https://bugzilla.redhat.com'
|
||||||
|
BZUSER = '{{ bugzilla_user }}'
|
||||||
|
BZPASS = '{{ bugzilla_password }}'
|
||||||
|
BZCOMPAPI = 'component.get'
|
||||||
|
FASUSER = '{{ fedorathirdpartyUser }}'
|
||||||
|
FASPASS = '{{ fedorathirdpartyPassword }}'
|
||||||
|
BUGZILLA_OVERRIDE_REPO = 'releng/fedora-scm-requests'
|
||||||
|
NOTIFYEMAIL = [
|
||||||
|
'kevin@fedoraproject.org',
|
||||||
|
'pingou@fedoraproject.org',
|
||||||
|
'ralph@fedoraproject.org',
|
||||||
|
'mprahl@fedoraproject.org',
|
||||||
|
]
|
||||||
|
DRY_RUN = False
|
||||||
|
|
||||||
|
{% if env == 'staging' %}
|
||||||
|
FASURL = 'https://admin.stg.fedoraproject.org/accounts'
|
||||||
|
FASINSECURE = True
|
||||||
|
PAGUREURL = 'https://stg.pagure.io'
|
||||||
|
PAGURE_DIST_GIT_URL = 'https://src.stg.fedoraproject.org'
|
||||||
|
MDAPIURL = 'https://apps.stg.fedoraproject.org/mdapi/'
|
||||||
|
{% else %}
|
||||||
|
FASURL = 'https://admin.fedoraproject.org/accounts'
|
||||||
|
FASINSECURE = False
|
||||||
|
PAGUREURL = 'https://pagure.io'
|
||||||
|
PAGURE_DIST_GIT_URL = 'https://src.fedoraproject.org'
|
||||||
|
MDAPIURL = 'https://apps.fedoraproject.org/mdapi/'
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
EMAIL_FROM = 'accounts@fedoraproject.org'
|
||||||
|
DATA_CACHE = '/var/tmp/pagure_sync_bz.json'
|
||||||
|
|
||||||
|
PRODUCTS = {
|
||||||
|
'Fedora': 'Fedora',
|
||||||
|
'Fedora Container': 'Fedora Container Images',
|
||||||
|
'Fedora EPEL': 'Fedora EPEL',
|
||||||
|
}
|
||||||
|
|
||||||
|
# When querying for current info, take segments of 1000 packages a time
|
||||||
|
BZ_PKG_SEGMENT = 1000
|
||||||
|
|
||||||
|
|
||||||
|
TMPL_EMAIL_ADMIN = '''
|
||||||
|
The following errors were encountered while updating bugzilla with information
|
||||||
|
from the Package Database. Please have the problems taken care of:
|
||||||
|
|
||||||
|
%s
|
||||||
|
'''
|
||||||
|
|
||||||
|
# PkgDB sync bugzilla email
|
||||||
|
PKGDB_SYNC_BUGZILLA_EMAIL = """Greetings.
|
||||||
|
|
||||||
|
You are receiving this email because there's a problem with your
|
||||||
|
bugzilla.redhat.com account.
|
||||||
|
|
||||||
|
If you recently changed the email address associated with your
|
||||||
|
Fedora account in the Fedora Account System, it is now out of sync
|
||||||
|
with your bugzilla.redhat.com account. This leads to problems
|
||||||
|
with Fedora packages you own or are CC'ed on bug reports for.
|
||||||
|
|
||||||
|
Please take one of the following actions:
|
||||||
|
|
||||||
|
a) login to your old bugzilla.redhat.com account and change the email
|
||||||
|
address to match your current email in the Fedora account system.
|
||||||
|
https://bugzilla.redhat.com login, click preferences, account
|
||||||
|
information and enter new email address.
|
||||||
|
|
||||||
|
b) Create a new account in bugzilla.redhat.com to match your
|
||||||
|
email listed in your Fedora account system account.
|
||||||
|
https://bugzilla.redhat.com/ click 'new account' and enter email
|
||||||
|
address.
|
||||||
|
|
||||||
|
c) Change your Fedora Account System email to match your existing
|
||||||
|
bugzilla.redhat.com account.
|
||||||
|
https://admin.fedoraproject.org/accounts login, click on 'my account',
|
||||||
|
then 'edit' and change your email address.
|
||||||
|
|
||||||
|
If you have questions or concerns, please let us know.
|
||||||
|
|
||||||
|
Your prompt attention in this matter is appreciated.
|
||||||
|
|
||||||
|
The Fedora admins.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DataChangedError(Exception):
|
||||||
|
'''Raised when data we are manipulating changes while we're modifying it.'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def segment(iterable, chunk, fill=None):
|
||||||
|
'''Collect data into `chunk` sized block'''
|
||||||
|
args = [iter(iterable)] * chunk
|
||||||
|
return itertools.izip_longest(*args, fillvalue=fill)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCache(dict):
|
||||||
|
def __init__(self, bz, acls):
|
||||||
|
self.bz = bz
|
||||||
|
self.acls = acls
|
||||||
|
|
||||||
|
# Ask bugzilla for a section of the pkglist.
|
||||||
|
# Save the information from the section that we want.
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return super(ProductCache, self).__getitem__(key)
|
||||||
|
except KeyError:
|
||||||
|
# We can only cache products we have pagure information for
|
||||||
|
if key not in self.acls:
|
||||||
|
raise
|
||||||
|
|
||||||
|
if BZCOMPAPI == 'getcomponentsdetails':
|
||||||
|
# Old API -- in python-bugzilla. But with current server, this
|
||||||
|
# gives ProxyError
|
||||||
|
products = self.bz.getcomponentsdetails(key)
|
||||||
|
elif BZCOMPAPI == 'component.get':
|
||||||
|
# Way that's undocumented in the partner-bugzilla api but works
|
||||||
|
# currently
|
||||||
|
pkglist = projects_dict[key].keys()
|
||||||
|
products = {}
|
||||||
|
for pkg_segment in segment(pkglist, BZ_PKG_SEGMENT):
|
||||||
|
# Format that bugzilla will understand. Strip None's that segment() pads
|
||||||
|
# out the final data segment() with
|
||||||
|
query = [
|
||||||
|
dict(product=PRODUCTS[key], component=p)
|
||||||
|
for p in pkg_segment if p is not None
|
||||||
|
]
|
||||||
|
raw_data = self.bz._proxy.Component.get(dict(names=query))
|
||||||
|
for package in raw_data['components']:
|
||||||
|
# Reformat data to be the same as what's returned from
|
||||||
|
# getcomponentsdetails
|
||||||
|
product = dict(initialowner=package['default_assignee'],
|
||||||
|
description=package['description'],
|
||||||
|
initialqacontact=package['default_qa_contact'],
|
||||||
|
initialcclist=package['default_cc'])
|
||||||
|
products[package['name'].lower()] = product
|
||||||
|
self[key] = products
|
||||||
|
|
||||||
|
return super(ProductCache, self).__getitem__(key)
|
||||||
|
|
||||||
|
|
||||||
|
class Bugzilla(object):
|
||||||
|
|
||||||
|
def __init__(self, bzServer, username, password, acls):
|
||||||
|
self.bzXmlRpcServer = bzServer
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
self.server = bugzilla.Bugzilla(
|
||||||
|
url=self.bzXmlRpcServer,
|
||||||
|
user=self.username,
|
||||||
|
password=self.password)
|
||||||
|
self.productCache = ProductCache(self.server, acls)
|
||||||
|
|
||||||
|
# Connect to the fedora account system
|
||||||
|
self.fas = AccountSystem(
|
||||||
|
base_url=FASURL,
|
||||||
|
username=FASUSER,
|
||||||
|
password=FASPASS)
|
||||||
|
self.userCache = self.fas.people_by_key(
|
||||||
|
key='username',
|
||||||
|
fields=['bugzilla_email'])
|
||||||
|
|
||||||
|
def _get_bugzilla_email(self, username):
|
||||||
|
'''Return the bugzilla email address for a user.
|
||||||
|
|
||||||
|
First looks in a cache for a username => bugzilla email. If not found,
|
||||||
|
reloads the cache from fas and tries again.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
return self.userCache[username]['bugzilla_email'].lower()
|
||||||
|
except KeyError:
|
||||||
|
if username.startswith('@'):
|
||||||
|
group = self.fas.group_by_name(username[1:])
|
||||||
|
self.userCache[username] = {
|
||||||
|
'bugzilla_email': group.mailing_list}
|
||||||
|
else:
|
||||||
|
person = self.fas.person_by_username(username)
|
||||||
|
bz_email = person.get('bugzilla_email', None)
|
||||||
|
if bz_email is None:
|
||||||
|
print('%s has no bugzilla email, valid account?'
|
||||||
|
% username)
|
||||||
|
else:
|
||||||
|
self.userCache[username] = {'bugzilla_email': bz_email}
|
||||||
|
return self.userCache[username]['bugzilla_email'].lower()
|
||||||
|
|
||||||
|
def add_edit_component(self, package, collection, owner, description=None,
|
||||||
|
qacontact=None, cclist=None):
|
||||||
|
'''Add or update a component to have the values specified.
|
||||||
|
'''
|
||||||
|
# Turn the cclist into something usable by bugzilla
|
||||||
|
if not cclist or 'people' not in cclist:
|
||||||
|
initialCCList = list()
|
||||||
|
else:
|
||||||
|
initialCCList = [
|
||||||
|
self._get_bugzilla_email(cc) for cc in cclist['people']]
|
||||||
|
if 'groups' in cclist:
|
||||||
|
group_cc = [
|
||||||
|
self._get_bugzilla_email(cc) for cc in cclist['groups']]
|
||||||
|
initialCCList.extend(group_cc)
|
||||||
|
|
||||||
|
# Add owner to the cclist so comaintainers taking over a bug don't
|
||||||
|
# have to do this manually
|
||||||
|
owner = self._get_bugzilla_email(owner)
|
||||||
|
if owner not in initialCCList:
|
||||||
|
initialCCList.append(owner)
|
||||||
|
|
||||||
|
# Lookup product
|
||||||
|
try:
|
||||||
|
product = self.productCache[collection]
|
||||||
|
except xmlrpclib.Fault as e:
|
||||||
|
# Output something useful in args
|
||||||
|
e.args = (e.faultCode, e.faultString)
|
||||||
|
raise
|
||||||
|
except xmlrpclib.ProtocolError as e:
|
||||||
|
e.args = ('ProtocolError', e.errcode, e.errmsg)
|
||||||
|
raise
|
||||||
|
|
||||||
|
pkgKey = package.lower()
|
||||||
|
if pkgKey in product:
|
||||||
|
# edit the package information
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
# Grab bugzilla email for things changable via xmlrpc
|
||||||
|
if qacontact:
|
||||||
|
qacontact = self._get_bugzilla_email(qacontact)
|
||||||
|
else:
|
||||||
|
qacontact = 'extras-qa@fedoraproject.org'
|
||||||
|
|
||||||
|
# Check for changes to the owner, qacontact, or description
|
||||||
|
if product[pkgKey]['initialowner'] != owner:
|
||||||
|
data['initialowner'] = owner
|
||||||
|
|
||||||
|
if description and product[pkgKey]['description'] != description:
|
||||||
|
data['description'] = description
|
||||||
|
if product[pkgKey]['initialqacontact'] != qacontact and (
|
||||||
|
qacontact or product[pkgKey]['initialqacontact']):
|
||||||
|
data['initialqacontact'] = qacontact
|
||||||
|
|
||||||
|
if len(product[pkgKey]['initialcclist']) != len(initialCCList):
|
||||||
|
data['initialcclist'] = initialCCList
|
||||||
|
else:
|
||||||
|
for ccMember in product[pkgKey]['initialcclist']:
|
||||||
|
if ccMember not in initialCCList:
|
||||||
|
data['initialcclist'] = initialCCList
|
||||||
|
break
|
||||||
|
|
||||||
|
if data:
|
||||||
|
### FIXME: initialowner has been made mandatory for some
|
||||||
|
# reason. Asking dkl why.
|
||||||
|
data['initialowner'] = owner
|
||||||
|
|
||||||
|
# Changes occurred. Submit a request to change via xmlrpc
|
||||||
|
data['product'] = PRODUCTS[collection]
|
||||||
|
data['component'] = package
|
||||||
|
if DRY_RUN:
|
||||||
|
print('[EDITCOMP] Changing via editComponent('
|
||||||
|
'%s, %s, "xxxxx")' % (data, self.username))
|
||||||
|
print('[EDITCOMP] Former values: %s|%s|%s|%s' % (
|
||||||
|
product[pkgKey]['initialowner'],
|
||||||
|
product[pkgKey]['description'],
|
||||||
|
product[pkgKey]['initialqacontact'],
|
||||||
|
product[pkgKey]['initialcclist']))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.server.editcomponent(data)
|
||||||
|
except xmlrpclib.Fault as e:
|
||||||
|
# Output something useful in args
|
||||||
|
e.args = (data, e.faultCode, e.faultString)
|
||||||
|
raise
|
||||||
|
except xmlrpclib.ProtocolError as e:
|
||||||
|
e.args = ('ProtocolError', e.errcode, e.errmsg)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# Add component
|
||||||
|
if qacontact:
|
||||||
|
qacontact = self._get_bugzilla_email(qacontact)
|
||||||
|
else:
|
||||||
|
qacontact = 'extras-qa@fedoraproject.org'
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'product': PRODUCTS[collection],
|
||||||
|
'component': package,
|
||||||
|
'description': description or 'NA',
|
||||||
|
'initialowner': owner,
|
||||||
|
'initialqacontact': qacontact
|
||||||
|
}
|
||||||
|
if initialCCList:
|
||||||
|
data['initialcclist'] = initialCCList
|
||||||
|
|
||||||
|
if DRY_RUN:
|
||||||
|
print('[ADDCOMP] Adding new component AddComponent:('
|
||||||
|
'%s, %s, "xxxxx")' % (data, self.username))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.server.addcomponent(data)
|
||||||
|
except xmlrpclib.Fault as e:
|
||||||
|
# Output something useful in args
|
||||||
|
e.args = (data, e.faultCode, e.faultString)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def send_email(fromAddress, toAddress, subject, message, ccAddress=None):
|
||||||
|
'''Send an email if there's an error.
|
||||||
|
|
||||||
|
This will be replaced by sending messages to a log later.
|
||||||
|
'''
|
||||||
|
msg = Message()
|
||||||
|
msg.add_header('To', ','.join(toAddress))
|
||||||
|
msg.add_header('From', fromAddress)
|
||||||
|
msg.add_header('Subject', subject)
|
||||||
|
if ccAddress is not None:
|
||||||
|
msg.add_header('Cc', ','.join(ccAddress))
|
||||||
|
toAddress = toAddress + ccAddress
|
||||||
|
msg.set_payload(message)
|
||||||
|
smtp = smtplib.SMTP('bastion')
|
||||||
|
smtp.sendmail(fromAddress, toAddress, msg.as_string())
|
||||||
|
smtp.quit()
|
||||||
|
|
||||||
|
|
||||||
|
def notify_users(errors):
|
||||||
|
''' Browse the list of errors and when we can retrieve the email
|
||||||
|
address, use it to notify the user about the issue.
|
||||||
|
'''
|
||||||
|
data = {}
|
||||||
|
if os.path.exists(DATA_CACHE):
|
||||||
|
try:
|
||||||
|
with open(DATA_CACHE) as stream:
|
||||||
|
data = json.load(stream)
|
||||||
|
except Exception as err:
|
||||||
|
print('Could not read the json file at %s: \nError: %s' % (
|
||||||
|
DATA_CACHE, err))
|
||||||
|
|
||||||
|
new_data = {}
|
||||||
|
seen = []
|
||||||
|
for error in errors:
|
||||||
|
notify_user = False
|
||||||
|
if 'The name ' in error and ' is not a valid username' in error:
|
||||||
|
user_email = error.split(' is not a valid username')[0].split(
|
||||||
|
'The name ')[1].strip()
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
# See if we already know about this user
|
||||||
|
if user_email in data and data[user_email]['last_update']:
|
||||||
|
last_update = datetime.datetime.fromtimestamp(
|
||||||
|
int(data[user_email]['last_update']))
|
||||||
|
# Only notify users once per hour
|
||||||
|
if (now - last_update).seconds >= 3600:
|
||||||
|
notify_user = True
|
||||||
|
else:
|
||||||
|
new_data[user_email] = data[user_email]
|
||||||
|
elif not data or user_email not in data:
|
||||||
|
notify_user = True
|
||||||
|
|
||||||
|
# Ensure we notify the user only once, no matter how many errors we
|
||||||
|
# got concerning them.
|
||||||
|
if user_email not in seen:
|
||||||
|
seen.append(user_email)
|
||||||
|
else:
|
||||||
|
notify_user = False
|
||||||
|
|
||||||
|
if notify_user:
|
||||||
|
send_email(
|
||||||
|
EMAIL_FROM,
|
||||||
|
[user_email],
|
||||||
|
subject='Please fix your bugzilla.redhat.com account',
|
||||||
|
message=PKGDB_SYNC_BUGZILLA_EMAIL,
|
||||||
|
ccAddress=NOTIFYEMAIL,
|
||||||
|
)
|
||||||
|
|
||||||
|
new_data[user_email] = {
|
||||||
|
'last_update': time.mktime(now.timetuple())
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(DATA_CACHE, 'w') as stream:
|
||||||
|
json.dump(new_data, stream)
|
||||||
|
|
||||||
|
|
||||||
|
def pagure_project_to_acl_schema(pagure_project, product):
|
||||||
|
"""
|
||||||
|
This function translates the JSON of a Pagure project to what PkgDB used to
|
||||||
|
output in the Bugzilla API.
|
||||||
|
:param pagure_project: a dictionary of the JSON of a Pagure project
|
||||||
|
:return: a dictionary of the content that the Bugzilla API would output
|
||||||
|
"""
|
||||||
|
session = retry_session()
|
||||||
|
base_error_msg = ('The connection to "{0}" failed with the status code '
|
||||||
|
'{1} and output "{2}"')
|
||||||
|
watchers_api_url = '{0}/api/0/{1}/{2}/watchers'.format(
|
||||||
|
PAGURE_DIST_GIT_URL.rstrip('/'), pagure_project['namespace'],
|
||||||
|
pagure_project['name'])
|
||||||
|
if DRY_RUN:
|
||||||
|
print('Querying {0}'.format(watchers_api_url))
|
||||||
|
watchers_rv = session.get(watchers_api_url, timeout=60)
|
||||||
|
if not watchers_rv.ok:
|
||||||
|
error_msg = base_error_msg.format(
|
||||||
|
watchers_api_url, watchers_rv.status_code, watchers_rv.text)
|
||||||
|
raise RuntimeError(error_msg)
|
||||||
|
watchers_rv_json = watchers_rv.json()
|
||||||
|
|
||||||
|
user_cc_list = []
|
||||||
|
for user, watch_levels in watchers_rv_json['watchers'].items():
|
||||||
|
# Only people watching issues should be CC'd
|
||||||
|
if 'issues' in watch_levels:
|
||||||
|
user_cc_list.append(user)
|
||||||
|
|
||||||
|
summary = None
|
||||||
|
if pagure_project['namespace'] != 'rpms':
|
||||||
|
mdapi_url = '{0}/rawhide/srcpkg/{1}'.format(
|
||||||
|
MDAPIURL.rstrip('/'), pagure_project['name'])
|
||||||
|
if DRY_RUN:
|
||||||
|
print('Querying {0}'.format(mdapi_url))
|
||||||
|
mdapi_rv = session.get(mdapi_url, timeout=60)
|
||||||
|
if mdapi_rv.ok:
|
||||||
|
mdapi_rv_json = mdapi_rv.json()
|
||||||
|
summary = mdapi_rv_json['summary']
|
||||||
|
elif not mdapi_rv.ok and mdapi_rv.status_code != 404:
|
||||||
|
error_msg = base_error_msg.format(
|
||||||
|
mdapi_url, mdapi_rv.status_code, mdapi_rv.text)
|
||||||
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
|
# Check if the Bugzilla ticket assignee has been overridden
|
||||||
|
owner = pagure_project['access_users']['owner'][0]
|
||||||
|
pagure_override_url = '{0}/{1}/raw/master/f/{2}/{3}'.format(
|
||||||
|
PAGUREURL.rstrip('/'), BUGZILLA_OVERRIDE_REPO, project['namespace'],
|
||||||
|
project['name'])
|
||||||
|
|
||||||
|
override_rv = session.get(pagure_override_url, timeout=30)
|
||||||
|
if override_rv.status_code == 200:
|
||||||
|
override_yaml = yaml.load(override_rv.text)
|
||||||
|
override_yaml = override_yaml.get('bugzilla_contact', {})
|
||||||
|
if override_yaml.get(product) \
|
||||||
|
and isinstance(override_yaml[product], string_types):
|
||||||
|
owner = override_yaml[product]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'cclist': {
|
||||||
|
# Groups is empty because you can't have groups watch projects.
|
||||||
|
# This is done only at the user level.
|
||||||
|
'groups': [],
|
||||||
|
'people': user_cc_list
|
||||||
|
},
|
||||||
|
'owner': owner,
|
||||||
|
# No package has this set in PkgDB's API, so it can be safely turned
|
||||||
|
# off and set to the defaults later on in the code
|
||||||
|
'qacontact': None,
|
||||||
|
'summary': summary
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Script syncing information between Pagure and bugzilla'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--debug', dest='debug', action='store_true', default=False,
|
||||||
|
help='Print the changes instead of making them in bugzilla')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
DRY_RUN = True
|
||||||
|
|
||||||
|
# Non-fatal errors to alert people about
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
projects_dict = {
|
||||||
|
'Fedora': {},
|
||||||
|
'Fedora Container': {},
|
||||||
|
'Fedora EPEL': {},
|
||||||
|
}
|
||||||
|
pagure_rpms_api_url = ('{0}/api/0/projects?&namespace=rpms&page=1&'
|
||||||
|
'per_page=100'.format(
|
||||||
|
PAGURE_DIST_GIT_URL.rstrip('/')))
|
||||||
|
session = retry_session()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if DRY_RUN:
|
||||||
|
print('Querying {0}'.format(pagure_rpms_api_url))
|
||||||
|
rv_json = session.get(pagure_rpms_api_url, timeout=120).json()
|
||||||
|
for project in rv_json['projects']:
|
||||||
|
pagure_project_branches_api_url = (
|
||||||
|
'{0}/api/0/rpms/{1}/git/branches'
|
||||||
|
.format(PAGURE_DIST_GIT_URL.rstrip('/'), project['name']))
|
||||||
|
branch_rv_json = session.get(
|
||||||
|
pagure_project_branches_api_url, timeout=60).json()
|
||||||
|
epel = False
|
||||||
|
fedora = False
|
||||||
|
for branch in branch_rv_json['branches']:
|
||||||
|
if re.match(r'epel\d+', branch):
|
||||||
|
epel = True
|
||||||
|
projects_dict['Fedora EPEL'][project['name']] = \
|
||||||
|
pagure_project_to_acl_schema(project, 'Fedora EPEL')
|
||||||
|
else:
|
||||||
|
fedora = True
|
||||||
|
projects_dict['Fedora'][project['name']] = \
|
||||||
|
pagure_project_to_acl_schema(project, 'Fedora')
|
||||||
|
|
||||||
|
if fedora and epel:
|
||||||
|
break
|
||||||
|
|
||||||
|
if rv_json['pagination']['next']:
|
||||||
|
pagure_rpms_api_url = rv_json['pagination']['next']
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
pagure_container_api_url = (
|
||||||
|
'{0}/api/0/projects?&namespace=container&page=1&per_page=100'
|
||||||
|
.format(PAGURE_DIST_GIT_URL))
|
||||||
|
while True:
|
||||||
|
if DRY_RUN:
|
||||||
|
print('Querying {0}'.format(pagure_container_api_url))
|
||||||
|
rv_json = session.get(pagure_container_api_url, timeout=120).json()
|
||||||
|
for project in rv_json['projects']:
|
||||||
|
project_pkgdb_schema = pagure_project_to_acl_schema(project)
|
||||||
|
projects_dict['Fedora Container'][project['name']] = \
|
||||||
|
project_pkgdb_schema
|
||||||
|
|
||||||
|
if rv_json['pagination']['next']:
|
||||||
|
pagure_container_api_url = rv_json['pagination']['next']
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Initialize the connection to bugzilla
|
||||||
|
bugzilla = Bugzilla(BZSERVER, BZUSER, BZPASS, projects_dict)
|
||||||
|
|
||||||
|
for product in projects_dict.keys():
|
||||||
|
if product not in PRODUCTS:
|
||||||
|
continue
|
||||||
|
for pkg in sorted(projects_dict[product]):
|
||||||
|
if DRY_RUN:
|
||||||
|
print(pkg)
|
||||||
|
pkgInfo = projects_dict[product][pkg]
|
||||||
|
try:
|
||||||
|
bugzilla.add_edit_component(
|
||||||
|
pkg,
|
||||||
|
product,
|
||||||
|
pkgInfo['owner'],
|
||||||
|
pkgInfo['summary'],
|
||||||
|
pkgInfo['qacontact'],
|
||||||
|
pkgInfo['cclist']
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
# A username didn't have a bugzilla address
|
||||||
|
errors.append(str(e.args))
|
||||||
|
except DataChangedError as e:
|
||||||
|
# A Package or Collection was returned via xmlrpc but wasn't
|
||||||
|
# present when we tried to change it
|
||||||
|
errors.append(str(e.args))
|
||||||
|
except xmlrpclib.ProtocolError as e:
|
||||||
|
# Unrecoverable and likely means that nothing is going to
|
||||||
|
# succeed.
|
||||||
|
errors.append(str(e.args))
|
||||||
|
break
|
||||||
|
except xmlrpclib.Error as e:
|
||||||
|
# An error occurred in the xmlrpc call. Shouldn't happen but
|
||||||
|
# we better see what it is
|
||||||
|
errors.append('%s -- %s' % (pkg, e.args[-1]))
|
||||||
|
|
||||||
|
# Send notification of errors
|
||||||
|
if errors:
|
||||||
|
if DRY_RUN:
|
||||||
|
print('[DEBUG]', '\n'.join(errors))
|
||||||
|
else:
|
||||||
|
notify_users(errors)
|
||||||
|
send_email(
|
||||||
|
EMAIL_FROM,
|
||||||
|
NOTIFYEMAIL,
|
||||||
|
'Errors while syncing bugzilla with the PackageDB',
|
||||||
|
TMPL_EMAIL_ADMIN % ('\n'.join(errors),))
|
||||||
|
else:
|
||||||
|
with open(DATA_CACHE, 'w') as stream:
|
||||||
|
json.dump({}, stream)
|
||||||
|
|
||||||
|
sys.exit(0)
|
|
@ -182,7 +182,7 @@ SSH_KEYS = {
|
||||||
# Configuration item that are specific for this odd pagure instance
|
# Configuration item that are specific for this odd pagure instance
|
||||||
|
|
||||||
PROJECT_TICKETS = False
|
PROJECT_TICKETS = False
|
||||||
ENABLE_NEW_PROJECTS = False
|
ENABLE_NEW_PROJECTS = True
|
||||||
ENABLE_DEL_PROJECTS = False
|
ENABLE_DEL_PROJECTS = False
|
||||||
ENABLE_TICKETS = False
|
ENABLE_TICKETS = False
|
||||||
ENABLE_GROUP_MNGT = False
|
ENABLE_GROUP_MNGT = False
|
||||||
|
@ -222,6 +222,7 @@ PDC_URL = 'https://pdc.fedoraproject.org/rest_api/v1/'
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
GITOLITE_BACKEND = 'distgit'
|
GITOLITE_BACKEND = 'distgit'
|
||||||
|
GITOLITE_CELERY_QUEUE = 'gitolite_queue'
|
||||||
|
|
||||||
THEME_TEMPLATE_FOLDER = '/usr/share/pagure_dist_git/template/'
|
THEME_TEMPLATE_FOLDER = '/usr/share/pagure_dist_git/template/'
|
||||||
|
|
||||||
|
@ -244,5 +245,9 @@ ADMIN_API_ACLS = [
|
||||||
'pull_request_comment',
|
'pull_request_comment',
|
||||||
'pull_request_merge',
|
'pull_request_merge',
|
||||||
'create_project',
|
'create_project',
|
||||||
|
'modify_project',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
BLACKLISTED_GROUPS = ['forks', 'group']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,3 +44,12 @@ WSGIDaemonProcess pagureproc user=pagure group=packager maximum-requests=1000 di
|
||||||
</Location>
|
</Location>
|
||||||
|
|
||||||
#</VirtualHost>
|
#</VirtualHost>
|
||||||
|
|
||||||
|
RewriteEngine on
|
||||||
|
|
||||||
|
# First try the instance-specific theme
|
||||||
|
RewriteCond "/usr/share/pagure_dist_git/static/$1" -f
|
||||||
|
RewriteRule "^/static/(.*)" "/usr/share/pagure_dist_git/static/$1" [L]
|
||||||
|
|
||||||
|
# Use the application default theme for files not customized
|
||||||
|
RewriteRule "^/static/(.*)" "/usr/lib/python2.7/site-packages/pagure/static/$1" [L]
|
||||||
|
|
|
@ -323,7 +323,7 @@ HAYSTACK_CONNECTIONS = {
|
||||||
#
|
#
|
||||||
Q_CLUSTER = {
|
Q_CLUSTER = {
|
||||||
'timeout': 300,
|
'timeout': 300,
|
||||||
'save_limit': 500,
|
'save_limit': 100000,
|
||||||
'orm': 'default',
|
'orm': 'default',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -282,3 +282,5 @@ CROSS_PROJECT_ACLS = [
|
||||||
'issue_create',
|
'issue_create',
|
||||||
'issue_comment',
|
'issue_comment',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
BLACKLISTED_GROUPS = ['forks', 'group']
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue