openSUSE-release-tools/manager_42.py

324 lines
13 KiB
Python
Raw Normal View History

2015-08-04 08:01:32 +02:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2015 SUSE Linux GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import argparse
import itertools
import logging
import sys
from xml.etree import cElementTree as ET
import osc.conf
import osc.core
import urllib2
import sys
import time
import yaml
2015-08-04 08:01:32 +02:00
from osclib.memoize import memoize
2016-04-08 16:52:40 +02:00
OPENSUSE = 'openSUSE:Leap:42.2'
2015-08-04 08:01:32 +02:00
makeurl = osc.core.makeurl
http_GET = osc.core.http_GET
http_DELETE = osc.core.http_DELETE
http_PUT = osc.core.http_PUT
2015-08-04 16:46:32 +02:00
http_POST = osc.core.http_POST
2015-08-04 08:01:32 +02:00
class UpdateCrawler(object):
2015-08-17 13:36:31 +02:00
def __init__(self, from_prj, caching = True):
2015-08-04 08:01:32 +02:00
self.from_prj = from_prj
2015-08-17 13:36:31 +02:00
self.caching = caching
2015-08-04 08:01:32 +02:00
self.apiurl = osc.conf.config['apiurl']
self.project_preference_order = [
'SUSE:SLE-12-SP1:Update',
'SUSE:SLE-12-SP1:GA',
'SUSE:SLE-12:Update',
'SUSE:SLE-12:GA',
2016-05-09 13:17:47 +02:00
'openSUSE:Leap:42.1:Update',
'openSUSE:Leap:42.1',
'openSUSE:Factory',
]
self.parse_lookup()
self.fill_package_meta()
2015-08-04 22:26:45 +02:00
self.packages = dict()
for project in [self.from_prj] + self.project_preference_order:
2015-08-04 22:26:45 +02:00
self.packages[project] = self.get_source_packages(project)
2015-08-13 11:09:29 +02:00
def latest_packages(self):
data = self.cached_GET(makeurl(self.apiurl,
['project', 'latest_commits', self.from_prj]))
lc = ET.fromstring(data)
packages = set()
for entry in lc.findall('{http://www.w3.org/2005/Atom}entry'):
title = entry.find('{http://www.w3.org/2005/Atom}title').text
if title.startswith('In '):
packages.add(title[3:].split(' ')[0])
return sorted(packages)
def parse_lookup(self):
2016-04-08 16:52:40 +02:00
self.lookup = yaml.safe_load(self._load_lookup_file())
self.lookup_changes = 0
def _load_lookup_file(self):
return self.cached_GET(makeurl(self.apiurl,
['source', self.from_prj, '00Meta', 'lookup.yml']))
def _put_lookup_file(self, data):
return http_PUT(makeurl(self.apiurl,
['source', self.from_prj, '00Meta', 'lookup.yml']), data=data)
def store_lookup(self):
2016-04-08 16:52:40 +02:00
if self.lookup_changes == 0:
logging.info('no change to lookup.yml')
return
data = yaml.dump(self.lookup, default_flow_style=False, explicit_start=True)
self._put_lookup_file(data)
self.lookup_changes = 0
2015-08-17 13:36:31 +02:00
@memoize()
def _cached_GET(self, url):
return self.retried_GET(url).read()
2015-08-17 13:36:31 +02:00
def cached_GET(self, url):
if self.caching:
2015-08-19 13:40:55 +02:00
return self._cached_GET(url)
return self.retried_GET(url).read()
def retried_GET(self, url):
try:
return http_GET(url)
except urllib2.HTTPError, e:
2015-08-30 17:00:21 +02:00
if 500 <= e.code <= 599:
2016-04-08 16:52:40 +02:00
logging.warn('Retrying {}'.format(url))
time.sleep(1)
return self.retried_GET(url)
raise e
2015-08-17 13:36:31 +02:00
2015-08-04 08:01:32 +02:00
def get_source_packages(self, project, expand=False):
"""Return the list of packages in a project."""
query = {'expand': 1} if expand else {}
2015-08-17 13:36:31 +02:00
root = ET.fromstring(
self.cached_GET(makeurl(self.apiurl,
2015-08-04 08:01:32 +02:00
['source', project],
2015-08-17 13:36:31 +02:00
query=query)))
2015-08-04 08:01:32 +02:00
packages = [i.get('name') for i in root.findall('entry')]
return packages
def _get_source_package(self, project, package, revision):
opts = { 'view': 'info' }
if revision:
opts['rev'] = revision
2015-08-17 13:36:31 +02:00
return self.cached_GET(makeurl(self.apiurl,
['source', project, package], opts))
2015-08-13 11:09:29 +02:00
def crawl(self, given_packages = None):
2015-08-04 08:01:32 +02:00
"""Main method of the class that run the crawler."""
self.try_to_find_left_packages(given_packages or self.packages[self.from_prj])
self.store_lookup()
2016-04-11 14:00:04 +02:00
def get_package_history(self, project, package, deleted = False):
try:
query = {}
if deleted:
query['deleted'] = 1
return self.cached_GET(makeurl(self.apiurl,
['source', project, package, '_history'], query))
except urllib2.HTTPError, e:
if e.code == 404:
return None
raise
2016-04-11 14:00:04 +02:00
def check_source_in_project(self, project, package, verifymd5, deleted=False):
if project not in self.packages:
self.packages[project] = self.get_source_packages(project)
2016-04-11 14:00:04 +02:00
if not deleted and not package in self.packages[project]:
return None, None
2015-08-17 13:36:31 +02:00
2016-04-11 14:00:04 +02:00
his = self.get_package_history(project, package, deleted)
if his is None:
return None, None
2015-08-13 11:09:29 +02:00
2015-08-04 16:46:32 +02:00
his = ET.fromstring(his)
historyrevs = dict()
2015-08-04 16:46:32 +02:00
revs = list()
for rev in his.findall('revision'):
historyrevs[rev.find('srcmd5').text] = rev.get('rev')
2015-08-04 16:46:32 +02:00
revs.append(rev.find('srcmd5').text)
revs.reverse()
for i in range(min(len(revs), 5)): # check last commits
2015-08-04 16:46:32 +02:00
srcmd5=revs.pop(0)
2015-08-17 13:36:31 +02:00
root = self.cached_GET(makeurl(self.apiurl,
['source', project, package], { 'rev': srcmd5, 'view': 'info'}))
2015-08-04 16:46:32 +02:00
root = ET.fromstring(root)
if root.get('verifymd5') == verifymd5:
return srcmd5, historyrevs[srcmd5]
return None, None
2015-08-13 11:09:29 +02:00
# check if we can find the srcmd5 in any of our underlay
# projects
2015-08-04 08:01:32 +02:00
def try_to_find_left_packages(self, packages):
for package in sorted(packages):
lproject = self.lookup.get(package, None)
if not package in self.packages[self.from_prj]:
2016-04-08 16:52:40 +02:00
logging.info("{} vanished".format(package))
if self.lookup.get(package):
del self.lookup[package]
self.lookup_changes += 1
continue
2015-08-04 08:01:32 +02:00
root = ET.fromstring(self._get_source_package(self.from_prj, package, None))
pm = self.package_metas[package]
devel = pm.find('devel')
2016-04-08 16:52:40 +02:00
if devel is not None or lproject.startswith('Devel;'):
develprj = None
develpkg = None
if devel is None:
(dummy, develprj, develpkg) = lproject.split(';')
logging.warn('{} lacks devel project setting {}/{}'.format(package, develprj, develpkg))
else:
develprj = devel.get('project')
develpkg = devel.get('package')
srcmd5, rev = self.check_source_in_project(develprj, develpkg,
root.get('verifymd5'))
if srcmd5:
2016-04-08 16:52:40 +02:00
lstring = 'Devel;{};{}'.format(develprj, develpkg)
if lstring != self.lookup[package]:
2016-04-08 16:52:40 +02:00
logging.debug("{} from devel {}/{} (was {})".format(package, develprj, develpkg, lproject))
self.lookup[package] = lstring
self.lookup_changes += 1
else:
2016-04-08 16:52:40 +02:00
logging.debug("{} lookup from {}/{} is correct".format(package, develprj, develpkg))
continue
2015-08-04 16:46:32 +02:00
linked = root.find('linked')
if not linked is None and linked.get('package') != package:
2016-04-08 16:52:40 +02:00
lstring = 'subpackage of {}'.format(linked.get('package'))
if lstring != lproject:
logging.warn("link mismatch: %s <> %s, subpackage? (was {})", linked.get('package'), package, lproject)
self.lookup[package] = lstring
self.lookup_changes += 1
else:
logging.debug("{} correctly marked as subpackage of {}".format(package, linked.get('package')))
2015-08-04 16:46:32 +02:00
continue
if lproject and lproject != 'FORK':
srcmd5, rev = self.check_source_in_project(lproject, package, root.get('verifymd5'))
if srcmd5:
2016-04-08 16:52:40 +02:00
logging.debug("{} lookup from {} is correct".format(package, lproject))
continue
2016-04-11 14:00:04 +02:00
if lproject == 'openSUSE:Factory':
his = self.get_package_history(lproject, package, deleted=True)
if his:
logging.debug("{} got dropped from {}".format(package, lproject))
continue
2015-08-19 18:02:45 +02:00
logging.debug("check where %s came from", package)
2015-08-17 13:36:31 +02:00
foundit = False
for project in self.project_preference_order:
srcmd5, rev = self.check_source_in_project(project, package, root.get('verifymd5'))
if srcmd5:
2016-04-08 16:52:40 +02:00
logging.info('{} -> {} (was {})'.format(package, project, lproject))
self.lookup[package] = project
self.lookup_changes += 1
2015-08-17 13:36:31 +02:00
foundit = True
break
2016-04-11 14:00:04 +02:00
2015-08-17 13:36:31 +02:00
if not foundit:
2016-04-08 16:52:40 +02:00
if lproject == 'FORK':
logging.debug("{}: lookup is correctly marked as fork".format(package))
else:
logging.info('{} is a fork (was {})'.format(package, lproject))
self.lookup[package] = 'FORK'
self.lookup_changes += 1
2015-08-04 16:46:32 +02:00
# avoid loosing too much work
if self.lookup_changes > 50:
self.store_lookup()
2015-08-04 22:26:45 +02:00
def get_link(self, project, package):
try:
2015-08-17 13:36:31 +02:00
link = self.cached_GET(makeurl(self.apiurl,
['source', project, package, '_link']))
2015-08-04 22:26:45 +02:00
except urllib2.HTTPError:
return None
return ET.fromstring(link)
2015-08-13 11:09:29 +02:00
def fill_package_meta(self):
self.package_metas = dict()
url = makeurl(self.apiurl, ['search', 'package'], "match=[@project='%s']" % self.from_prj)
2016-04-08 16:52:40 +02:00
root = ET.fromstring(self.cached_GET(url))
for p in root.findall('package'):
name = p.attrib['name']
self.package_metas[name] = p
2015-08-19 17:29:09 +02:00
2015-08-13 11:09:29 +02:00
2015-08-04 08:01:32 +02:00
def main(args):
# Configure OSC
osc.conf.get_config(override_apiurl=args.apiurl)
osc.conf.config['debug'] = args.debug
2015-08-19 13:40:55 +02:00
uc = UpdateCrawler(args.from_prj, caching = args.cache_requests )
given_packages = args.packages
if not args.all and not given_packages:
given_packages = uc.latest_packages()
uc.crawl(given_packages)
2015-08-13 11:09:29 +02:00
2015-08-04 08:01:32 +02:00
if __name__ == '__main__':
2016-04-08 16:52:40 +02:00
description = 'maintain %s/00Meta/lookup.yml' % OPENSUSE
2015-08-04 08:01:32 +02:00
parser = argparse.ArgumentParser(description=description)
parser.add_argument('-A', '--apiurl', metavar='URL', help='API URL')
parser.add_argument('-d', '--debug', action='store_true',
help='print info useful for debuging')
parser.add_argument('-a', '--all', action='store_true',
help='check all packages')
2015-08-04 08:01:32 +02:00
parser.add_argument('-f', '--from', dest='from_prj', metavar='PROJECT',
help='project where to get the updates (default: %s)' % OPENSUSE,
default=OPENSUSE)
2015-08-12 16:39:35 +02:00
parser.add_argument('-n', '--dry', action='store_true',
help='dry run, no POST, PUT, DELETE')
2015-08-19 13:40:55 +02:00
parser.add_argument('--cache-requests', action='store_true', default=False,
help='cache GET requests. Not recommended for daily use.')
parser.add_argument("packages", nargs='*', help="packages to check")
2015-08-04 08:01:32 +02:00
args = parser.parse_args()
# Set logging configuration
logging.basicConfig(level=logging.DEBUG if args.debug
else logging.INFO)
2015-08-12 16:39:35 +02:00
if args.dry:
def dryrun(t, *args, **kwargs):
return lambda *args, **kwargs: logging.debug("dryrun %s %s %s", t, args, str(kwargs)[:200])
2015-08-12 16:39:35 +02:00
http_POST = dryrun('POST')
http_PUT = dryrun('PUT')
http_DELETE = dryrun('DELETE')
2015-08-04 08:01:32 +02:00
sys.exit(main(args))
2015-08-17 13:36:31 +02:00
# vim:sw=4 et