2019-05-27 18:02:15 +02:00
|
|
|
#!/usr/bin/python3
|
2017-02-11 11:15:50 +01:00
|
|
|
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
from pprint import pprint
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import re
|
|
|
|
import logging
|
|
|
|
from optparse import OptionParser
|
|
|
|
import cmdln
|
|
|
|
import requests as REQ
|
|
|
|
import json
|
2018-05-15 15:44:57 +02:00
|
|
|
import time
|
2018-11-16 08:32:25 +01:00
|
|
|
|
2019-05-27 18:02:15 +02:00
|
|
|
from urllib.error import HTTPError
|
2017-02-11 11:15:50 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
from xml.etree import cElementTree as ET
|
|
|
|
except ImportError:
|
|
|
|
import cElementTree as ET
|
|
|
|
|
2017-06-10 18:09:27 +02:00
|
|
|
try:
|
|
|
|
import cPickle as pickle
|
|
|
|
except:
|
|
|
|
import pickle
|
|
|
|
|
2017-02-11 11:15:50 +01:00
|
|
|
import osc.conf
|
|
|
|
import osc.core
|
2018-09-04 15:03:03 -05:00
|
|
|
from osclib.cache_manager import CacheManager
|
2017-02-11 11:15:50 +01:00
|
|
|
import ReviewBot
|
|
|
|
|
|
|
|
from osclib.comments import CommentAPI
|
|
|
|
|
2018-05-15 15:44:57 +02:00
|
|
|
http_GET = osc.core.http_GET
|
2017-02-11 11:15:50 +01:00
|
|
|
|
2018-05-15 15:47:43 +02:00
|
|
|
|
2017-02-11 11:15:50 +01:00
|
|
|
class LegalAuto(ReviewBot.ReviewBot):
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
self.legaldb = None
|
2017-05-11 10:31:09 +02:00
|
|
|
self.legaldb_headers = {}
|
2017-02-11 11:15:50 +01:00
|
|
|
self.apinick = None
|
|
|
|
self.message = None
|
2017-03-29 17:20:50 +02:00
|
|
|
if self.ibs:
|
2017-02-11 11:15:50 +01:00
|
|
|
self.apinick = 'ibs#'
|
2017-03-29 17:20:50 +02:00
|
|
|
else:
|
2017-02-11 11:15:50 +01:00
|
|
|
self.apinick = 'obs#'
|
2018-05-15 15:47:43 +02:00
|
|
|
self.override_allow = False # Handled via external tool.
|
2018-04-16 18:27:58 -05:00
|
|
|
self.request_default_return = True
|
2017-03-29 17:20:50 +02:00
|
|
|
|
2018-05-15 15:44:57 +02:00
|
|
|
def retried_GET(self, url):
|
|
|
|
try:
|
|
|
|
return http_GET(url)
|
2019-04-20 21:16:50 +02:00
|
|
|
except HTTPError as e:
|
2018-05-15 15:44:57 +02:00
|
|
|
if 500 <= e.code <= 599:
|
2019-04-20 21:16:50 +02:00
|
|
|
print('Retrying {}'.format(url))
|
2018-05-15 15:44:57 +02:00
|
|
|
time.sleep(1)
|
|
|
|
return self.retried_GET(url)
|
|
|
|
raise e
|
|
|
|
|
2017-03-29 17:20:50 +02:00
|
|
|
def request_priority(self):
|
|
|
|
prio = self.request.priority or 'moderate'
|
|
|
|
prios = {'low': 1, 'moderate': 2, 'important': 3, 'critical': 4}
|
|
|
|
prio = prios.get(prio, 4) * 2
|
|
|
|
if self.ibs:
|
|
|
|
prio = prio + 1
|
|
|
|
return prio
|
2017-02-11 11:15:50 +01:00
|
|
|
|
|
|
|
def request_nick(self, id=None):
|
|
|
|
if not id:
|
|
|
|
id = self.request.reqid
|
|
|
|
return self.apinick + id
|
|
|
|
|
|
|
|
def create_db_entry(self, src_project, src_package, src_rev):
|
|
|
|
params = {'api': self.apiurl, 'project': src_project, 'package': src_package,
|
|
|
|
'external_link': self.request_nick(),
|
2017-06-11 11:45:06 +02:00
|
|
|
'created': self.request.statehistory[0].when + ' UTC'}
|
2017-02-11 11:15:50 +01:00
|
|
|
if src_rev:
|
|
|
|
params['rev'] = src_rev
|
|
|
|
url = osc.core.makeurl(self.legaldb, ['packages'], params)
|
2017-05-11 10:31:09 +02:00
|
|
|
|
|
|
|
package = REQ.post(url, headers=self.legaldb_headers).json()
|
2017-02-11 11:15:50 +01:00
|
|
|
if not 'saved' in package:
|
|
|
|
return None
|
|
|
|
package = package['saved']
|
|
|
|
url = osc.core.makeurl(self.legaldb, ['requests'], {'external_link': self.request_nick(),
|
|
|
|
'package': package['id']})
|
2017-05-11 10:31:09 +02:00
|
|
|
request = REQ.post(url, headers=self.legaldb_headers).json()
|
2017-02-11 11:15:50 +01:00
|
|
|
return [package['id']]
|
|
|
|
|
|
|
|
def check_source_submission(self, src_project, src_package, src_rev, target_project, target_package):
|
|
|
|
self.logger.info("%s/%s@%s -> %s/%s" % (src_project,
|
|
|
|
src_package, src_rev, target_project, target_package))
|
|
|
|
to_review = self.open_reviews.get(self.request_nick(), None)
|
2018-05-15 15:44:57 +02:00
|
|
|
if to_review:
|
2018-05-15 15:47:43 +02:00
|
|
|
self.logger.info("Found " + json.dumps(to_review))
|
2017-02-11 11:15:50 +01:00
|
|
|
to_review = to_review or self.create_db_entry(
|
|
|
|
src_project, src_package, src_rev)
|
|
|
|
if not to_review:
|
|
|
|
return None
|
|
|
|
for pack in to_review:
|
|
|
|
url = osc.core.makeurl(self.legaldb, ['package', str(pack)])
|
2017-05-11 10:31:09 +02:00
|
|
|
report = REQ.get(url, headers=self.legaldb_headers).json()
|
2017-03-29 17:20:50 +02:00
|
|
|
if report.get('priority', 0) != self.request_priority():
|
2019-04-20 21:16:50 +02:00
|
|
|
print('Update priority {}'.format(self.request_priority()))
|
2017-03-29 17:20:50 +02:00
|
|
|
url = osc.core.makeurl(
|
|
|
|
self.legaldb, ['package', str(pack)], {'priority': self.request_priority()})
|
2017-05-11 10:31:09 +02:00
|
|
|
REQ.patch(url, headers=self.legaldb_headers)
|
2017-02-11 11:15:50 +01:00
|
|
|
state = report.get('state', 'BROKEN')
|
|
|
|
if state == 'obsolete':
|
|
|
|
url = osc.core.makeurl(self.legaldb, ['packages', 'import', str(pack)], {
|
|
|
|
'result': 'reopened in obs', 'state': 'new'})
|
2017-05-11 10:31:09 +02:00
|
|
|
package = REQ.post(url, headers=self.legaldb_headers).json()
|
2017-02-11 11:15:50 +01:00
|
|
|
# reopen
|
|
|
|
return None
|
|
|
|
if not state in ['acceptable', 'correct', 'unacceptable']:
|
|
|
|
return None
|
|
|
|
if state == 'unacceptable':
|
|
|
|
user = report.get('reviewing_user', None)
|
|
|
|
if not user:
|
|
|
|
self.message = 'declined'
|
2018-05-15 15:44:57 +02:00
|
|
|
print("unacceptable without user %d" % report.get('id'))
|
2017-02-11 11:15:50 +01:00
|
|
|
return None
|
2019-02-22 17:04:56 +01:00
|
|
|
comment = report.get('result', None).encode('utf-8')
|
2017-02-11 11:15:50 +01:00
|
|
|
if comment:
|
|
|
|
self.message = "@{} declined the legal report with the following comment: {}".format(
|
|
|
|
user, comment)
|
|
|
|
else:
|
|
|
|
self.message = "@{} declined the legal report".format(user)
|
|
|
|
return None
|
|
|
|
return False
|
|
|
|
# print url, json.dumps(report)
|
|
|
|
self.message = 'ok'
|
|
|
|
return True
|
|
|
|
|
2018-05-15 15:44:57 +02:00
|
|
|
def check_one_request(self, req):
|
|
|
|
self.message = None
|
|
|
|
result = super(LegalAuto, self).check_one_request(req)
|
|
|
|
if result is None and self.message is not None:
|
|
|
|
print(self.message, req.reqid)
|
|
|
|
return result
|
|
|
|
|
|
|
|
def check_action__default(self, req, a):
|
|
|
|
self.logger.error("unhandled request type %s" % a.type)
|
|
|
|
return True
|
|
|
|
|
2017-02-11 11:15:50 +01:00
|
|
|
def prepare_review(self):
|
|
|
|
url = osc.core.makeurl(self.legaldb, ['requests'])
|
2018-05-15 15:44:57 +02:00
|
|
|
req = REQ.get(url, headers=self.legaldb_headers)
|
|
|
|
req = req.json()
|
2017-02-11 11:15:50 +01:00
|
|
|
self.open_reviews = {}
|
|
|
|
requests = []
|
|
|
|
for hash in req['requests']:
|
|
|
|
ext_link = str(hash['external_link'])
|
|
|
|
self.open_reviews[ext_link] = list(set(hash['packages']))
|
|
|
|
if ext_link.startswith(self.apinick):
|
|
|
|
rq = ext_link[len(self.apinick):]
|
|
|
|
requests.append('@id=' + rq)
|
|
|
|
while len(requests):
|
|
|
|
batch = requests[:200]
|
|
|
|
requests = requests[200:]
|
|
|
|
match = "(state/@name='declined' or state/@name='revoked' or state/@name='superseded')"
|
|
|
|
match += ' and (' + ' or '.join(sorted(batch)) + ')'
|
|
|
|
url = osc.core.makeurl(
|
|
|
|
self.apiurl, ['search', 'request', 'id'], {'match': match})
|
|
|
|
# prefer POST because of the length
|
|
|
|
root = ET.parse(osc.core.http_POST(url)).getroot()
|
|
|
|
for request in root.findall('request'):
|
|
|
|
self.delete_from_db(request.get('id'))
|
|
|
|
|
|
|
|
def delete_from_db(self, id):
|
|
|
|
url = osc.core.makeurl(
|
|
|
|
self.legaldb, ['requests'], {'external_link': self.request_nick(id)})
|
2017-05-11 10:31:09 +02:00
|
|
|
REQ.delete(url, headers=self.legaldb_headers)
|
2017-02-11 11:15:50 +01:00
|
|
|
|
|
|
|
# overload as we need to get of the bot_request
|
|
|
|
def _set_review(self, req, state):
|
|
|
|
if self.dryrun:
|
|
|
|
self.logger.debug("dry setting %s to %s with %s" %
|
|
|
|
(req.reqid, state, self.message))
|
|
|
|
return
|
|
|
|
|
|
|
|
self.logger.debug("setting %s to %s" % (req.reqid, state))
|
|
|
|
osc.core.change_review_state(apiurl=self.apiurl,
|
|
|
|
reqid=req.reqid, newstate=state,
|
|
|
|
by_group=self.review_group,
|
|
|
|
by_user=self.review_user, message=self.message)
|
|
|
|
self.delete_from_db(req.reqid)
|
|
|
|
|
2017-06-10 18:09:27 +02:00
|
|
|
def _pkl_path(self):
|
2018-09-04 15:03:03 -05:00
|
|
|
return CacheManager.directory('legal-auto')
|
2017-06-10 18:09:27 +02:00
|
|
|
|
|
|
|
def update_project(self, project):
|
|
|
|
try:
|
|
|
|
with open(self._pkl_path(), 'rb') as pkl_file:
|
|
|
|
self.pkg_cache = pickle.load(pkl_file)
|
|
|
|
except (IOError, EOFError):
|
|
|
|
self.pkg_cache = {}
|
|
|
|
|
2018-05-15 15:44:57 +02:00
|
|
|
self.packages = []
|
2017-06-12 11:27:54 +02:00
|
|
|
# we can't create packages for requests - we need a nonewpackages=1 first
|
2018-05-15 15:47:43 +02:00
|
|
|
# self._query_requests(project)
|
2017-06-10 18:09:27 +02:00
|
|
|
self._query_sources(project)
|
2018-05-15 15:44:57 +02:00
|
|
|
self._save_pkl()
|
|
|
|
url = osc.core.makeurl(self.legaldb, ['products', project])
|
|
|
|
request = REQ.patch(url, headers=self.legaldb_headers, data={'id': self.packages}).json()
|
2017-06-10 18:09:27 +02:00
|
|
|
|
|
|
|
def _save_pkl(self):
|
|
|
|
pkl_file = open(self._pkl_path(), 'wb')
|
|
|
|
pickle.dump(self.pkg_cache, pkl_file)
|
|
|
|
pkl_file.close()
|
|
|
|
|
|
|
|
def _query_sources(self, project):
|
|
|
|
url = osc.core.makeurl(
|
|
|
|
self.apiurl, ['source', project], {'view': 'info'})
|
2018-05-15 15:44:57 +02:00
|
|
|
f = self.retried_GET(url)
|
2017-06-10 18:09:27 +02:00
|
|
|
root = ET.parse(f).getroot()
|
2018-05-15 15:44:57 +02:00
|
|
|
#root = ET.fromstring(open('prj.info').read())
|
2017-06-10 18:09:27 +02:00
|
|
|
for si in root.findall('sourceinfo'):
|
|
|
|
if si.findall('error'):
|
|
|
|
continue
|
|
|
|
package = si.get('package')
|
|
|
|
if ':' in package:
|
|
|
|
continue
|
|
|
|
if package == 'patchinfo' or package.startswith('patchinfo.'):
|
|
|
|
continue
|
|
|
|
# handle maintenance links - we only want the latest
|
|
|
|
match = re.match(r'(\S+)\.\d+$', package)
|
|
|
|
if match:
|
|
|
|
if si.find('filename').text == match.group(1) + '.spec':
|
|
|
|
continue
|
2018-05-15 15:44:57 +02:00
|
|
|
match = re.match(r'(\S+)\.imported_\d+$', package)
|
|
|
|
if match:
|
2018-05-15 15:47:43 +02:00
|
|
|
continue
|
2018-05-15 15:44:57 +02:00
|
|
|
skip = False
|
|
|
|
for l in si.findall('linked'):
|
2018-05-15 15:47:43 +02:00
|
|
|
lpackage = l.get('package')
|
2018-05-15 15:44:57 +02:00
|
|
|
# strip sle11's .imported_ suffix
|
2018-05-15 15:47:43 +02:00
|
|
|
lpackage = re.sub(r'\.imported_\d+$', '', lpackage)
|
2018-05-15 15:44:57 +02:00
|
|
|
# check if the lpackage is origpackage.NUMBER
|
2018-05-15 15:47:43 +02:00
|
|
|
match = re.match(r'(\S+)\.\d+$', lpackage)
|
|
|
|
if match and match.group(1) == package:
|
|
|
|
lpackage = package
|
2018-05-15 15:44:57 +02:00
|
|
|
if package != lpackage:
|
2019-04-20 21:16:50 +02:00
|
|
|
print("SKIP", package, "it links to", lpackage)
|
2018-05-15 15:47:43 +02:00
|
|
|
skip = True
|
|
|
|
break
|
|
|
|
if skip:
|
|
|
|
continue
|
2018-05-15 15:44:57 +02:00
|
|
|
self.packages.append(self._add_source(project, project, package, si.get('rev')))
|
2017-06-10 18:09:27 +02:00
|
|
|
|
|
|
|
def _query_requests(self, project):
|
|
|
|
match = "(state/@name='new' or state/@name='review') and (action/target/@project='{}')"
|
|
|
|
match = match.format(project)
|
|
|
|
url = osc.core.makeurl(
|
|
|
|
self.apiurl, ['search', 'request'], {'match': match})
|
|
|
|
f = osc.core.http_GET(url)
|
|
|
|
root = ET.parse(f).getroot()
|
|
|
|
for rq in root.findall('request'):
|
|
|
|
for a in rq.findall('action'):
|
|
|
|
if not a.attrib['type'] in ('submit', 'maintenance_incident'):
|
|
|
|
continue
|
|
|
|
source = a.find('source')
|
|
|
|
revision = source.attrib.get('rev')
|
|
|
|
src_project = source.attrib['project']
|
|
|
|
package = source.attrib['package']
|
|
|
|
self._add_source(project, src_project, package, revision)
|
|
|
|
|
|
|
|
def _add_source(self, tproject, sproject, package, revision):
|
|
|
|
params = {'api': self.apiurl, 'project': sproject, 'package': package,
|
|
|
|
'external_link': tproject}
|
|
|
|
if revision:
|
|
|
|
params['rev'] = revision
|
|
|
|
hkey = hash(json.dumps(params, sort_keys=True))
|
|
|
|
if hkey in self.pkg_cache:
|
|
|
|
return self.pkg_cache[hkey]
|
|
|
|
|
|
|
|
params['priority'] = 1
|
|
|
|
url = osc.core.makeurl(self.legaldb, ['packages'], params)
|
|
|
|
|
|
|
|
try:
|
|
|
|
obj = REQ.post(url, headers=self.legaldb_headers).json()
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
if not 'saved' in obj:
|
|
|
|
return None
|
2019-04-20 21:16:50 +02:00
|
|
|
print("PKG", tproject, sproject, package, revision, obj['saved']['id'])
|
2017-06-10 18:09:27 +02:00
|
|
|
self.pkg_cache[hkey] = obj['saved']['id']
|
|
|
|
return self.pkg_cache[hkey]
|
|
|
|
|
2017-02-11 11:15:50 +01:00
|
|
|
|
|
|
|
class CommandLineInterface(ReviewBot.CommandLineInterface):
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
ReviewBot.CommandLineInterface.__init__(self, args, kwargs)
|
|
|
|
self.clazz = LegalAuto
|
|
|
|
|
|
|
|
def get_optparser(self):
|
|
|
|
parser = ReviewBot.CommandLineInterface.get_optparser(self)
|
|
|
|
|
|
|
|
parser.add_option("--legaldb", dest='legaldb', metavar='URL',
|
|
|
|
default='http://legaldb.suse.de', help="Use different legaldb deployment")
|
|
|
|
return parser
|
|
|
|
|
2017-06-10 18:09:27 +02:00
|
|
|
def do_project(self, subcmd, opts, *projects):
|
|
|
|
"""${cmd_name}: Overloaded to create/update product
|
|
|
|
"""
|
|
|
|
for project in projects:
|
|
|
|
self.checker.update_project(project)
|
|
|
|
|
2017-02-11 11:15:50 +01:00
|
|
|
def setup_checker(self):
|
|
|
|
if not self.options.user and not self.options.group:
|
|
|
|
self.options.group = 'legal-auto'
|
|
|
|
bot = ReviewBot.CommandLineInterface.setup_checker(self)
|
|
|
|
bot.legaldb = self.options.legaldb
|
2019-05-27 18:02:15 +02:00
|
|
|
bot.legaldb_headers['Authorization'] = 'Token ' + osc.conf.config['legaldb_token']
|
2017-02-11 11:15:50 +01:00
|
|
|
return bot
|
|
|
|
|
2018-05-15 15:47:43 +02:00
|
|
|
|
2017-02-11 11:15:50 +01:00
|
|
|
if __name__ == "__main__":
|
|
|
|
requests_log = logging.getLogger("requests.packages.urllib3")
|
|
|
|
requests_log.setLevel(logging.WARNING)
|
|
|
|
requests_log.propagate = False
|
|
|
|
|
|
|
|
app = CommandLineInterface()
|
|
|
|
sys.exit(app.main())
|