Hello community,
here is the log from the commit of package openSUSE-release-tools for
openSUSE:Factory checked in at 2019-02-24 17:19:20
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openSUSE-release-tools (Old)
and /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openSUSE-release-tools"
Sun Feb 24 17:19:20 2019 rev:165 rq:678068 version:20190221.324b92a
Changes:
--------
---
/work/SRC/openSUSE:Factory/openSUSE-release-tools/openSUSE-release-tools.changes
2019-02-17 12:21:20.384200580 +0100
+++
/work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.28833/openSUSE-release-tools.changes
2019-02-24 17:19:37.744405356 +0100
@@ -1,0 +2,52 @@
+Thu Feb 21 18:16:24 UTC 2019 - [email protected]
+
+- Update to version 20190221.324b92a:
+ * Output something every 5 minutes to show activity on the console
+
+-------------------------------------------------------------------
+Wed Feb 20 21:07:30 UTC 2019 - [email protected]
+
+- Update to version 20190220.d43faf0:
+ * tests/obs: provide offering to appease the re-implementation of OBS.
+ * travis: add check to complain about product references in origin-manager.
+ * dist/package: provide origin-manager sub-package.
+ * systemd: provide osrt-origin-manager service and timer.
+ * origin-manager: provide ReviewBot utilizing osclib.origin.
+ * osclib/origin: provide origin management functions.
+ * dist/obs: provide OSRT:OriginConfig definition.
+ * osclib/util: project_version(): return 0 instead of None for invalid
project.
+ * osclib/util: provide project_list_family_prior_pattern().
+ * osclib/util: project_list_family_prior(): provide include_updates option.
+ * osclib/util: project_list_family(): cache via memoize.
+ * osclib/util: project_list_family(): provide include_update option.
+ * osclib/util: project_list_family(): handle :NonFree suffix.
+ * osclib/cache: handle repetative package_source_hash_history() calls.
+ * osclib/conf: properly load config for innerconnect projects.
+ * osclib/core: provide request_remote_identifier() for printable identifier.
+ * osclib/core: provide issue_tracker*() functions.
+ * osclib/core: provide review_*() functions for summarizing review state.
+ * osclib/core: provide project_remote_*() functions for innerconnect
projects.
+ * osclib/core: provide package_source_hash*() functions.
+ * osclib/core: provide entity_source_link().
+ * osclib/conf: add repo-checker key since it does review :Update requests.
+ * osclib/core: provide entity_exists() and use in StagingAPI.item_exists().
+
+-------------------------------------------------------------------
+Tue Feb 19 06:49:32 UTC 2019 - [email protected]
+
+- Update to version 20190219.8d3c53c:
+ * Need apiurl for staging report
+
+-------------------------------------------------------------------
+Mon Feb 18 09:21:38 UTC 2019 - [email protected]
+
+- Update to version 20190218.0f466a0:
+ * Add kubic-kured-image and kubic-pause-image to container_products
+
+-------------------------------------------------------------------
+Fri Feb 15 13:43:35 UTC 2019 - [email protected]
+
+- Update to version 20190215.53bbe03:
+ * Add missing dependency
+
+-------------------------------------------------------------------
Old:
----
openSUSE-release-tools-20190213.be751cc.obscpio
New:
----
openSUSE-release-tools-20190221.324b92a.obscpio
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ openSUSE-release-tools.spec ++++++
--- /var/tmp/diff_new_pack.L1hfoj/_old 2019-02-24 17:19:38.692405187 +0100
+++ /var/tmp/diff_new_pack.L1hfoj/_new 2019-02-24 17:19:38.696405187 +0100
@@ -20,7 +20,7 @@
%define source_dir openSUSE-release-tools
%define announcer_filename factory-package-news
Name: openSUSE-release-tools
-Version: 20190213.be751cc
+Version: 20190221.324b92a
Release: 0
Summary: Tools to aid in staging and release work for openSUSE/SUSE
License: GPL-2.0-or-later AND MIT
@@ -188,6 +188,16 @@
%description metrics-access
Ingest download.o.o Apache access logs and generate metrics.
+%package origin-manager
+Summary: Package origin management tools
+Group: Development/Tools/Other
+BuildArch: noarch
+Requires: osclib = %{version}
+Requires(pre): shadow
+
+%description origin-manager
+Tools for managing the origin of package sources and keeping them in sync.
+
%package repo-checker
Summary: Repository checker service
Group: Development/Tools/Other
@@ -247,6 +257,8 @@
Requires: osclib = %{version}
Requires: python-requests
Requires: python-solv
+# for compressing the .packages files in 000update-repos
+Requires: /usr/bin/xz
# we use the same user as repo-checker
PreReq: openSUSE-release-tools-repo-checker
@@ -389,6 +401,14 @@
/usr/bin/systemctl try-restart --no-block osrt-obs-operator
fi
+%pre origin-manager
+getent passwd osrt-origin-manager > /dev/null || \
+ useradd -r -m -s /sbin/nologin -c "user for
openSUSE-release-tools-origin-manager" osrt-origin-manager
+exit 0
+
+%postun origin-manager
+%systemd_postun
+
%pre repo-checker
getent passwd osrt-repo-checker > /dev/null || \
useradd -r -m -s /sbin/nologin -c "user for
openSUSE-release-tools-repo-checker" osrt-repo-checker
@@ -466,6 +486,7 @@
%exclude %{_datadir}/%{source_dir}/metrics
%exclude %{_datadir}/%{source_dir}/metrics.py
%exclude %{_datadir}/%{source_dir}/metrics_release.py
+%exclude %{_datadir}/%{source_dir}/origin-manager.py
%exclude %{_bindir}/osrt-staging-report
%exclude %{_datadir}/%{source_dir}/pkglistgen
%exclude %{_datadir}/%{source_dir}/pkglistgen.py
@@ -571,6 +592,11 @@
%{_bindir}/osrt-obs_operator
%{_unitdir}/osrt-obs-operator.service
+%files origin-manager
+%{_bindir}/osrt-origin-manager
+%{_unitdir}/osrt-origin-manager.service
+%{_unitdir}/osrt-origin-manager.timer
+
%files repo-checker
%defattr(-,root,root,-)
%{_bindir}/osrt-repo_checker
++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.L1hfoj/_old 2019-02-24 17:19:38.728405181 +0100
+++ /var/tmp/diff_new_pack.L1hfoj/_new 2019-02-24 17:19:38.728405181 +0100
@@ -1,6 +1,6 @@
<servicedata>
<service name="tar_scm">
<param
name="url">https://github.com/openSUSE/openSUSE-release-tools.git</param>
- <param
name="changesrevision">9c852461d47438a5e6d12c9802d7d9550d448327</param>
+ <param
name="changesrevision">0e6951e653f9ee586cb71559de6b8422da721d25</param>
</service>
</servicedata>
++++++ openSUSE-release-tools-20190213.be751cc.obscpio ->
openSUSE-release-tools-20190221.324b92a.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/openSUSE-release-tools-20190213.be751cc/.travis.yml
new/openSUSE-release-tools-20190221.324b92a/.travis.yml
--- old/openSUSE-release-tools-20190213.be751cc/.travis.yml 2019-02-13
08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/.travis.yml 2019-02-21
19:08:06.000000000 +0100
@@ -47,6 +47,7 @@
- pip install flake8
script:
- flake8
+ - ./dist/ci/flake-extra
- env: TEST_SUITE=nosetests
sudo: required
services:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/PubSubConsumer.py
new/openSUSE-release-tools-20190221.324b92a/PubSubConsumer.py
--- old/openSUSE-release-tools-20190213.be751cc/PubSubConsumer.py
2019-02-13 08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/PubSubConsumer.py
2019-02-21 19:08:06.000000000 +0100
@@ -1,6 +1,7 @@
import logging
import pika
import sys
+from datetime import datetime
class PubSubConsumer(object):
"""This is an example consumer that will handle unexpected interactions
@@ -28,8 +29,24 @@
self._closing = False
self._consumer_tag = None
self._prefix = amqp_prefix
+ self._timer_id = None
self.logger = logger
+ def restart_timer(self):
+ interval = 300
+ if self._timer_id:
+ self._connection.remove_timeout(self._timer_id)
+ else:
+ # check the initial state on first timer hit
+ # so be quick about it
+ interval = 0
+ self._timer_id = self._connection.add_timeout(interval,
self.still_alive)
+
+ def still_alive(self):
+ # output something so gocd doesn't consider it stalled
+ self.logger.info('Still alive: {}'.format(datetime.now().time()))
+ self.restart_timer()
+
def connect(self):
"""This method connects to RabbitMQ, returning the connection handle.
When the connection is established, the on_connection_open method
@@ -263,6 +280,7 @@
"""
self.logger.debug('Issuing consumer related RPC commands')
self.add_on_cancel_callback()
+ self.restart_timer()
self._consumer_tag = self._channel.basic_consume(self.on_message,
self.queue_name,
no_ack=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/dist/ci/flake-extra
new/openSUSE-release-tools-20190221.324b92a/dist/ci/flake-extra
--- old/openSUSE-release-tools-20190213.be751cc/dist/ci/flake-extra
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/dist/ci/flake-extra
2019-02-21 19:08:06.000000000 +0100
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+# Should never contain references to products.
+! grep -iP 'leap|factory|sle' origin-manager.py osclib/origin.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/dist/obs/OSRT:OriginConfig.xml
new/openSUSE-release-tools-20190221.324b92a/dist/obs/OSRT:OriginConfig.xml
--- old/openSUSE-release-tools-20190213.be751cc/dist/obs/OSRT:OriginConfig.xml
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/dist/obs/OSRT:OriginConfig.xml
2019-02-21 19:08:06.000000000 +0100
@@ -0,0 +1,5 @@
+<definition name="OriginConfig" namespace="OSRT">
+ <description>OriginManager configuration</description>
+ <count>1</count>
+ <modifiable_by role="maintainer"/>
+</definition>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/dist/package/openSUSE-release-tools.spec
new/openSUSE-release-tools-20190221.324b92a/dist/package/openSUSE-release-tools.spec
---
old/openSUSE-release-tools-20190213.be751cc/dist/package/openSUSE-release-tools.spec
2019-02-13 08:50:17.000000000 +0100
+++
new/openSUSE-release-tools-20190221.324b92a/dist/package/openSUSE-release-tools.spec
2019-02-21 19:08:06.000000000 +0100
@@ -188,6 +188,16 @@
%description metrics-access
Ingest download.o.o Apache access logs and generate metrics.
+%package origin-manager
+Summary: Package origin management tools
+Group: Development/Tools/Other
+BuildArch: noarch
+Requires: osclib = %{version}
+Requires(pre): shadow
+
+%description origin-manager
+Tools for managing the origin of package sources and keeping them in sync.
+
%package repo-checker
Summary: Repository checker service
Group: Development/Tools/Other
@@ -247,6 +257,8 @@
Requires: osclib = %{version}
Requires: python-requests
Requires: python-solv
+# for compressing the .packages files in 000update-repos
+Requires: /usr/bin/xz
# we use the same user as repo-checker
PreReq: openSUSE-release-tools-repo-checker
@@ -389,6 +401,14 @@
/usr/bin/systemctl try-restart --no-block osrt-obs-operator
fi
+%pre origin-manager
+getent passwd osrt-origin-manager > /dev/null || \
+ useradd -r -m -s /sbin/nologin -c "user for
openSUSE-release-tools-origin-manager" osrt-origin-manager
+exit 0
+
+%postun origin-manager
+%systemd_postun
+
%pre repo-checker
getent passwd osrt-repo-checker > /dev/null || \
useradd -r -m -s /sbin/nologin -c "user for
openSUSE-release-tools-repo-checker" osrt-repo-checker
@@ -466,6 +486,7 @@
%exclude %{_datadir}/%{source_dir}/metrics
%exclude %{_datadir}/%{source_dir}/metrics.py
%exclude %{_datadir}/%{source_dir}/metrics_release.py
+%exclude %{_datadir}/%{source_dir}/origin-manager.py
%exclude %{_bindir}/osrt-staging-report
%exclude %{_datadir}/%{source_dir}/pkglistgen
%exclude %{_datadir}/%{source_dir}/pkglistgen.py
@@ -571,6 +592,11 @@
%{_bindir}/osrt-obs_operator
%{_unitdir}/osrt-obs-operator.service
+%files origin-manager
+%{_bindir}/osrt-origin-manager
+%{_unitdir}/osrt-origin-manager.service
+%{_unitdir}/osrt-origin-manager.timer
+
%files repo-checker
%defattr(-,root,root,-)
%{_bindir}/osrt-repo_checker
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/origin-manager.py
new/openSUSE-release-tools-20190221.324b92a/origin-manager.py
--- old/openSUSE-release-tools-20190213.be751cc/origin-manager.py
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/origin-manager.py
2019-02-21 19:08:06.000000000 +0100
@@ -0,0 +1,99 @@
+#!/usr/bin/python
+
+from osclib.core import package_source_hash
+from osclib.origin import origin_annotation_dump
+from osclib.origin import config_load
+from osclib.origin import origin_find
+from osclib.origin import policy_evaluate
+import ReviewBot
+import sys
+
+
+class OriginManager(ReviewBot.ReviewBot):
+ def __init__(self, *args, **kwargs):
+ ReviewBot.ReviewBot.__init__(self, *args, **kwargs)
+
+ # ReviewBot options.
+ self.request_default_return = True
+ # No such thing as override, only changing origin which must be
approved
+ # by fallback group. Annotation must be included in review.
+ self.override_allow = False
+
+ def check_source_submission(self, src_project, src_package, src_rev,
tgt_project, tgt_package):
+ if not self.config_validate(tgt_project):
+ return False
+
+ source_hash_new = package_source_hash(self.apiurl, src_project,
src_package, src_rev)
+ origin_info_new = origin_find(self.apiurl, tgt_project, tgt_package,
source_hash_new)
+
+ source_hash_old = package_source_hash(self.apiurl, tgt_project,
tgt_package)
+ origin_info_old = origin_find(self.apiurl, tgt_project, tgt_package,
source_hash_old, True)
+
+ result = policy_evaluate(self.apiurl, tgt_project, tgt_package,
+ origin_info_new, origin_info_old,
+ source_hash_new, source_hash_old)
+ return self.policy_result_handle(tgt_project, tgt_package,
origin_info_new, origin_info_old, result)
+
+ def config_validate(self, target_project):
+ config = config_load(self.apiurl, target_project)
+ if not config:
+ self.review_messages['declined'] = 'OSRT:OriginConfig attribute
missing'
+ return False
+ if not config.get('fallback-group'):
+ self.review_messages['declined'] =
'OSRT:OriginConfig.fallback-group missing'
+ return False
+ if not self.dryrun and config['review-user'] != self.review_user:
+ self.logger.warning(
+ 'OSRT:OriginConfig.review-user ({}) does not match
ReviewBot.review_user ({})'.format(
+ config['review-user'], self.review_user))
+
+ return True
+
+ def policy_result_handle(self, project, package, origin_info_new,
origin_info_old, result):
+ if len(result.reviews):
+ self.policy_result_reviews_add(project, package, result.reviews)
+
+ self.policy_result_comment_add(project, package, result.comments)
+
+ if not result.wait:
+ if result.accept:
+ self.review_messages['accepted'] =
origin_annotation_dump(origin_info_new, origin_info_old)
+ return result.accept
+
+ return None
+
+ def policy_result_reviews_add(self, project, package, reviews):
+ for key, comment in reviews.items():
+ if key == 'maintainer':
+ self.devel_project_review_ensure(self.request, project,
package, comment)
+ elif key == 'fallback':
+ fallback_group = config_load(self.apiurl,
project).get('fallback-group')
+ self.add_review(self.request, by_group=fallback_group,
msg=comment)
+ else:
+ self.add_review(self.request, by_group=key, msg=comment)
+
+ def policy_result_comment_add(self, project, package, comments):
+ message = '\n\n'.join(comments)
+ if len(self.request.actions) > 1:
+ message = '## {}/{}\n\n{}'.format(project, package, message)
+ suffix = '::'.join([project, package])
+ else:
+ suffix = None
+
+ only_replace = False
+ if not len(comments):
+ message = 'Previous comment no longer relevant.'
+ only_replace = True
+
+ self.comment_write(state='seen', message=message, identical=True,
+ only_replace=only_replace, bot_name_suffix=suffix)
+
+
+class CommandLineInterface(ReviewBot.CommandLineInterface):
+ def __init__(self, *args, **kwargs):
+ ReviewBot.CommandLineInterface.__init__(self, *args, **kwargs)
+ self.clazz = OriginManager
+
+if __name__ == "__main__":
+ app = CommandLineInterface()
+ sys.exit(app.main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/osclib/cache.py
new/openSUSE-release-tools-20190221.324b92a/osclib/cache.py
--- old/openSUSE-release-tools-20190213.be751cc/osclib/cache.py 2019-02-13
08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/osclib/cache.py 2019-02-21
19:08:06.000000000 +0100
@@ -99,6 +99,8 @@
r'/source$': TTL_LONG,
# Sources will be expired with project, could be done on package level.
r'/source/([^/?]+)(?:\?.*)?$': TTL_LONG,
+ # Handle origin-manager repetative package_source_hash_history() calls.
+ r'/source/([^/]+)/(?:[^/]+)/(?:_history)$': TTL_SHORT,
r'/source/([^/]+)/(?:[^/]+)/(?:_meta|_link)$': TTL_LONG,
r'/source/([^/]+)/dashboard/[^/]+': TTL_LONG,
r'/source/([^/]+)/_attribute/[^/]+': TTL_DUPLICATE,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/osclib/conf.py
new/openSUSE-release-tools-20190221.324b92a/osclib/conf.py
--- old/openSUSE-release-tools-20190213.be751cc/osclib/conf.py 2019-02-13
08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/osclib/conf.py 2019-02-21
19:08:06.000000000 +0100
@@ -121,6 +121,7 @@
r'openSUSE:(?P<project>Leap:(?P<version>[\d.]+)(?::NonFree)?:Update)$': {
'main-repo': 'standard',
'leaper-override-group': 'leap-reviewers',
+ 'repo-checker': 'repo-checker',
'repo_checker-arch-whitelist': 'x86_64',
'repo_checker-no-filter': 'True',
'repo_checker-package-comment-devel': 'True',
@@ -207,8 +208,12 @@
@memoize(session=True) # Allow reset by memoize_session_reset() for
ReviewBot.
def get(apiurl, project):
"""Cached version for directly accessing project config."""
- Config(apiurl, project)
- return conf.config.get(project, [])
+ # Properly handle loading the config for interconnect projects.
+ from osclib.core import project_remote_apiurl
+ apiurl_remote, project_remote = project_remote_apiurl(apiurl, project)
+
+ Config(apiurl_remote, project_remote)
+ return conf.config.get(project_remote, [])
@property
def conf(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/osclib/core.py
new/openSUSE-release-tools-20190221.324b92a/osclib/core.py
--- old/openSUSE-release-tools-20190213.be751cc/osclib/core.py 2019-02-13
08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/osclib/core.py 2019-02-21
19:08:06.000000000 +0100
@@ -21,6 +21,7 @@
from osc.core import makeurl
from osc.core import owner
from osc.core import Request
+from osc.core import search
from osc.core import show_package_meta
from osc.core import show_project_meta
from osc.core import show_results_meta
@@ -461,3 +462,197 @@
root = ET.fromstringlist(get_commitlog(
apiurl, project, '_project', None, format='xml', meta=True))
return int(root.find('logentry').get('revision'))
+
+def entity_exists(apiurl, project, package=None):
+ try:
+ http_GET(makeurl(apiurl, filter(None, ['source', project, package]) +
['_meta']))
+ except HTTPError as e:
+ if e.code == 404:
+ return False
+
+ raise e
+
+ return True
+
+def entity_source_link(apiurl, project, package=None):
+ try:
+ if package:
+ parts = ['source', project, package, '_link']
+ else:
+ parts = ['source', project, '_meta']
+ url = makeurl(apiurl, parts)
+ root = ETL.parse(http_GET(url)).getroot()
+ except HTTPError as e:
+ if e.code == 404:
+ return None
+
+ raise e
+
+ return root if package else root.find('link')
+
+@memoize(session=True)
+def package_source_link_copy(apiurl, project, package):
+ link = entity_source_link(apiurl, project, package)
+ return link is not None and link.get('cicount') == 'copy'
+
+# Ideally, all package_source_hash* functions would operate on srcmd5, but
+# unfortunately that is not practical for real use-cases. The srcmd5 includes
+# service run information in addition to the presence of a link even if the
+# expanded sources are identical. The verifymd5 sum excludes such information
+# and only covers the sources (as should be the point), but looks at the link
+# sources which means for projects like devel which link to the head revision
of
+# downstream all the verifymd5 sums are the same. This makes the summary md5s
+# provided by OBS useless for comparing source and really anything. Instead the
+# individual file md5s are used to generate a sha1 which is used for
comparison.
+# In the case of maintenance projects they are structured such that the updates
+# are suffixed packages and the unsuffixed package is empty and only links to
+# a specific suffixed package each revision. As such for maintenance projects
+# the link must be expanded and is safe to do so. Additionally, projects that
+# inherit packages need to same treatment (ie. expanding) until they are
+# overridden within the project.
+@memoize(session=True)
+def package_source_hash(apiurl, project, package, revision=None):
+ query = {}
+ if revision:
+ query['rev'] = revision
+
+ # Will not catch packages that previous had a link, but no longer do.
+ if package_source_link_copy(apiurl, project, package):
+ query['expand'] = 1
+
+ try:
+ url = makeurl(apiurl, ['source', project, package], query)
+ root = ETL.parse(http_GET(url)).getroot()
+ except HTTPError as e:
+ if e.code == 404:
+ return None
+
+ raise e
+
+ if revision and root.find('error') is not None:
+ # OBS returns XML error instead of HTTP 404 if revision not found.
+ return None
+
+ from osclib.util import sha1_short
+ return sha1_short(root.xpath('entry[@name!="_link"]/@md5'))
+
+def package_source_hash_history(apiurl, project, package, limit=5,
include_project_link=False):
+ try:
+ # get_commitlog() reverses the order so newest revisions are first.
+ root = ETL.fromstringlist(
+ get_commitlog(apiurl, project, package, None, format='xml'))
+ except HTTPError as e:
+ if e.code == 404:
+ return
+
+ raise e
+
+ if include_project_link:
+ source_hashes = []
+
+ source_md5s = root.xpath('logentry/@srcmd5')
+ for source_md5 in source_md5s[:limit]:
+ source_hash = package_source_hash(apiurl, project, package, source_md5)
+ yield source_hash
+
+ if include_project_link:
+ source_hashes.append(source_hash)
+
+ if include_project_link and (not limit or len(source_md5s) < limit):
+ link = entity_source_link(apiurl, project)
+ if link is None:
+ return
+ project = link.get('project')
+
+ if limit:
+ limit_remaining = limit - len(source_md5s)
+
+ # Allow small margin for duplicates.
+ for source_hash in package_source_hash_history(apiurl, project,
package, None, True):
+ if source_hash in source_hashes:
+ continue
+
+ yield source_hash
+
+ if limit:
+ limit_remaining += -1
+ if limit_remaining == 0:
+ break
+
+@memoize(session=True)
+def project_remote_list(apiurl):
+ remotes = {}
+
+ root = search(apiurl, project='starts-with(remoteurl, "http")')['project']
+ for project in root.findall('project'):
+ # Strip ending /public as the only use-cases for manually checking
+ # remote projects is to query them directly to use an API that does not
+ # work over the interconnect. As such /public will have same problem.
+ remotes[project.get('name')] = re.sub('/public$', '',
project.find('remoteurl').text)
+
+ return remotes
+
+def project_remote_apiurl(apiurl, project):
+ remotes = project_remote_list(apiurl)
+ for remote in remotes:
+ if project.startswith(remote + ':'):
+ return remotes[remote], project[len(remote) + 1:]
+
+ return apiurl, project
+
+def review_find_last(request, who):
+ for review in reversed(request.reviews):
+ if review.who == who:
+ return review
+
+ return None
+
+def reviews_remaining(request):
+ reviews = []
+ for review in request.reviews:
+ if review.state != 'accepted':
+ reviews.append(review_short(review))
+
+ return reviews
+
+def review_short(review):
+ if review.by_user:
+ return review.by_user
+ if review.by_group:
+ return review.by_group
+ if review.by_project:
+ if review.by_package:
+ return '/'.join([review.by_project, review.by_package])
+ return review.by_project
+
+ return None
+
+def issue_trackers(apiurl):
+ url = makeurl(apiurl, ['issue_trackers'])
+ root = ET.parse(http_GET(url)).getroot()
+ trackers = {}
+ for tracker in root.findall('issue-tracker'):
+ trackers[tracker.find('name').text] = tracker.find('label').text
+ return trackers
+
+def issue_tracker_by_url(apiurl, tracker_url):
+ url = makeurl(apiurl, ['issue_trackers'])
+ root = ETL.parse(http_GET(url)).getroot()
+ if not tracker_url.endswith('/'):
+ # All trackers are formatted with trailing slash.
+ tracker_url += '/'
+ return
next(iter(root.xpath('issue-tracker[url[text()="{}"]]'.format(tracker_url)) or
[]), None)
+
+def issue_tracker_label_apply(tracker, identifier):
+ return tracker.find('label').text.replace('@@@', identifier)
+
+def request_remote_identifier(apiurl, apiurl_remote, request_id):
+ if apiurl_remote == apiurl:
+ return 'request#{}'.format(request_id)
+
+ # The URL differences make this rather convoluted.
+ tracker = issue_tracker_by_url(apiurl, apiurl_remote.replace('api.',
'build.'))
+ if tracker is not None:
+ return issue_tracker_label_apply(tracker, request_id)
+
+ return request_id
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/osclib/origin.py
new/openSUSE-release-tools-20190221.324b92a/osclib/origin.py
--- old/openSUSE-release-tools-20190213.be751cc/osclib/origin.py
1970-01-01 01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/osclib/origin.py
2019-02-21 19:08:06.000000000 +0100
@@ -0,0 +1,475 @@
+from copy import deepcopy
+from collections import namedtuple
+import logging
+from osc.core import get_request_list
+from osclib.conf import Config
+from osclib.core import attribute_value_load
+from osclib.core import devel_project_get
+from osclib.core import entity_exists
+from osclib.core import package_source_hash
+from osclib.core import package_source_hash_history
+from osclib.core import project_remote_apiurl
+from osclib.core import review_find_last
+from osclib.core import reviews_remaining
+from osclib.core import request_remote_identifier
+from osclib.memoize import memoize
+from osclib.util import project_list_family
+from osclib.util import project_list_family_prior_pattern
+import re
+import yaml
+
+NAME = 'origin-manager'
+DEFAULTS = {
+ 'unknown_origin_wait': False,
+ 'origins': [],
+ 'review-user': '<config:origin-manager-review-user>',
+ 'fallback-group': '<config:origin-manager-fallback-group>',
+ 'fallback-workaround': {},
+}
+POLICY_DEFAULTS = {
+ 'additional_reviews': [],
+ 'automatic_updates': True,
+ 'maintainer_review_always': False,
+ 'maintainer_review_initial': True,
+ 'pending_submission_allow': False,
+ 'pending_submission_consider': False,
+ 'pending_submission_allowed_reviews': [
+ '<config_source:staging>*',
+ '<config_source:repo-checker>',
+ ],
+}
+
+OriginInfo = namedtuple('OriginInfo', ['project', 'pending'])
+PendingRequestInfo = namedtuple('PendingRequestInfo', ['identifier',
'reviews_remaining'])
+PolicyResult = namedtuple('PolicyResult', ['wait', 'accept', 'reviews',
'comments'])
+
+@memoize(session=True)
+def config_load(apiurl, project):
+ config = attribute_value_load(apiurl, project, 'OriginConfig')
+ if not config:
+ return {}
+
+ return config_resolve(apiurl, project, yaml.safe_load(config))
+
+def config_origin_generator(origins, apiurl=None, project=None, package=None,
skip_workarounds=False):
+ for origin_item in origins:
+ for origin, values in origin_item.items():
+ is_workaround = origin_workaround_check(origin)
+ if skip_workarounds and is_workaround:
+ break
+
+ if (origin == '<devel>' or origin == '<devel>~') and apiurl and
project and package:
+ devel_project, devel_package = devel_project_get(apiurl,
project, package)
+ if not devel_project:
+ break
+ origin = devel_project
+ if is_workaround:
+ origin = origin_workaround_ensure(origin)
+
+ yield origin, values
+ break # Only support single value inside list item.
+
+def config_resolve(apiurl, project, config):
+ defaults = POLICY_DEFAULTS.copy()
+ defaults_workarounds = POLICY_DEFAULTS.copy()
+
+ origins_original = config_origin_list(config)
+
+ config_project = Config.get(apiurl, project)
+ config_resolve_variables(config, config_project)
+
+ origins = config['origins']
+ i = 0
+ while i < len(origins):
+ origin = origins[i].keys()[0]
+ values = origins[i][origin]
+
+ if origin == '*':
+ del origins[i]
+ defaults.update(values)
+ defaults_workarounds.update(values)
+ config_resolve_apply(config, values, until='*')
+ elif origin == '*~':
+ del origins[i]
+ defaults_workarounds.update(values)
+ config_resolve_create_workarounds(config, values, origins_original)
+ config_resolve_apply(config, values, workaround=True, until='*~')
+ elif '*' in origin:
+ # Does not allow for family + workaround expansion (ie. foo*~).
+ del origins[i]
+ config_resolve_create_family(apiurl, project, config, i, origin,
values)
+ elif origin.endswith('~'):
+ values_new = deepcopy(defaults_workarounds)
+ values_new.update(values)
+ values.update(values_new)
+ i += 1
+ else:
+ values_new = deepcopy(defaults)
+ values_new.update(values)
+ values.update(values_new)
+ i += 1
+
+ return config
+
+def config_resolve_variables(config, config_project):
+ defaults_merged = DEFAULTS.copy()
+ defaults_merged.update(config)
+ config.update(defaults_merged)
+
+ for key in ['review-user', 'fallback-group']:
+ config[key] = config_resolve_variable(config[key], config_project)
+
+ if not config['review-user']:
+ config['review-user'] = NAME
+
+ for origin, values in config_origin_generator(config['origins']):
+ if 'additional_reviews' in values:
+ values['additional_reviews'] = [
+ config_resolve_variable(v, config_project) for v in
values['additional_reviews']]
+
+def config_resolve_variable(value, config_project, key='config'):
+ prefix = '<{}:'.format(key)
+ end = value.rfind('>')
+ if not value.startswith(prefix) or end == -1:
+ return value
+
+ key = value[len(prefix):end]
+ if key in config_project and config_project[key]:
+ return config_project[key] + value[end + 1:]
+ return ''
+
+def config_origin_list(config, apiurl=None, project=None, package=None,
skip_workarounds=False):
+ origin_list = []
+ for origin, values in config_origin_generator(
+ config['origins'], apiurl, project, package, skip_workarounds):
+ origin_list.append(origin)
+ return origin_list
+
+def config_resolve_create_workarounds(config, values_workaround, origins_skip):
+ origins = config['origins']
+ i = 0
+ for origin, values in config_origin_generator(origins):
+ i += 1
+ if origin.startswith('*') or origin.endswith('~'):
+ continue
+
+ origin_new = origin + '~'
+ if origin_new in origins_skip:
+ continue
+
+ values_new = deepcopy(values)
+ values_new.update(values_workaround)
+ origins.insert(i, { origin_new: values_new })
+
+def config_resolve_create_family(apiurl, project, config, position, origin,
values):
+ projects = project_list_family_prior_pattern(apiurl, origin, project)
+ for origin_expanded in reversed(projects):
+ config['origins'].insert(position, { origin_expanded: values })
+
+def config_resolve_apply(config, values_apply, key=None, workaround=False,
until=None):
+ for origin, values in config_origin_generator(config['origins']):
+ if workaround and (not origin.endswith('~') or origin == '*~'):
+ continue
+
+ if key:
+ if origin == key:
+ values.update(values)
+ continue
+
+ if until and origin == until:
+ break
+
+ values.update(values_apply)
+
+def origin_workaround_check(origin):
+ return origin.endswith('~')
+
+def origin_workaround_ensure(origin):
+ if not origin_workaround_check(origin):
+ return origin + '~'
+ return origin
+
+@memoize(session=True)
+def origin_find(apiurl, target_project, package, source_hash=None,
current=False,
+ pending_allow=True, fallback=True):
+ config = config_load(apiurl, target_project)
+
+ if not source_hash:
+ current = True
+ source_hash = package_source_hash(apiurl, target_project, package)
+ if not source_hash:
+ return None
+
+ logging.debug('origin_find: {}/{} with source {} ({}, {}, {})'.format(
+ target_project, package, source_hash, current, pending_allow,
fallback))
+
+ for origin, values in config_origin_generator(config['origins'], apiurl,
target_project, package, True):
+ if project_source_contain(apiurl, origin, package, source_hash):
+ return OriginInfo(origin, False)
+
+ if pending_allow and (values['pending_submission_allow'] or
values['pending_submission_consider']):
+ pending = project_source_pending(apiurl, origin, package,
source_hash)
+ if pending is not False:
+ return OriginInfo(origin, pending)
+
+ if not fallback:
+ return None
+
+ # Unable to find matching origin, if current fallback to last known origin
+ # and mark as workaround, otherwise return current origin as workaround.
+ if current:
+ origin_info = origin_find_fallback(apiurl, target_project, package,
source_hash, config['review-user'])
+ else:
+ origin_info = origin_find(apiurl, target_project, package)
+
+ if origin_info:
+ # Force origin to be workaround since required fallback.
+ origin = origin_workaround_ensure(origin_info.project)
+ if origin in config_origin_list(config, apiurl, target_project,
package):
+ return OriginInfo(origin, origin_info.pending)
+
+ return None
+
+def project_source_contain(apiurl, project, package, source_hash):
+ for source_hash_consider in package_source_hash_history(apiurl, project,
package):
+ project_source_log('contain', project, source_hash_consider,
source_hash)
+ if source_hash_consider == source_hash:
+ return True
+
+ return False
+
+def project_source_pending(apiurl, project, package, source_hash):
+ apiurl_remote, project_remote = project_remote_apiurl(apiurl, project)
+ requests = get_request_list(apiurl_remote, project_remote, package, None,
['new', 'review'], 'submit')
+ for request in requests:
+ for action in request.actions:
+ source_hash_consider = package_source_hash(
+ apiurl_remote, action.src_project, action.src_package,
action.src_rev)
+
+ project_source_log('pending', project, source_hash_consider,
source_hash)
+ if source_hash_consider == source_hash:
+ return PendingRequestInfo(
+ request_remote_identifier(apiurl, apiurl_remote,
request.reqid),
+ reviews_remaining(request))
+
+ return False
+
+def project_source_log(key, project, source_hash_consider, source_hash):
+ logging.debug('source_{}: {:<40} {} == {}{}'.format(
+ key, project, source_hash_consider, source_hash,
+ ' (match)' if source_hash_consider == source_hash else ''))
+
+def origin_find_fallback(apiurl, target_project, package, source_hash, user):
+ # Search accepted requests (newest to oldest), find the last review made by
+ # the specified user, load comment as annotation, and extract origin.
+ requests = get_request_list(apiurl, target_project, package, None,
['accepted'], 'submit')
+ for request in sorted(requests, key=lambda r: r.reqid, reverse=True):
+ review = review_find_last(request, user)
+ if not review:
+ continue
+
+ annotation = origin_annotation_load(review.comment)
+ return OriginInfo(annotation.get('origin'), False)
+
+ # Fallback to searching workaround project.
+ fallback_workaround = config_load(apiurl,
target_project).get('fallback-workaround')
+ if fallback_workaround:
+ if project_source_contain(apiurl, fallback_workaround['project'],
package, source_hash):
+ return OriginInfo(fallback_workaround['origin'], False)
+
+ # Attempt to find a revision of target package that matches an origin.
+ first = True
+ for source_hash_consider in package_source_hash_history(apiurl,
target_project, package):
+ if first:
+ first = False
+ continue
+
+ origin_info = origin_find(
+ apiurl, target_project, package, source_hash_consider,
pending_allow=False, fallback=False)
+ if origin_info:
+ return origin_info
+
+ return None
+
+def origin_annotation_dump(origin_info_new, origin_info_old):
+ data = {'origin': str(origin_info_new.project)}
+ if origin_info_old and origin_info_new.project != origin_info_old.project:
+ data['origin_old'] = str(origin_info_old.project)
+
+ return yaml.dump(data, default_flow_style=False)
+
+def origin_annotation_load(annotation):
+ # For some reason OBS insists on indenting every subsequent line which
+ # screws up yaml parsing since indentation has meaning.
+ return yaml.safe_load(re.sub(r'^\s+', '', annotation, flags=re.MULTILINE))
+
+def origin_find_highest(apiurl, project, package):
+ config = config_load(apiurl, project)
+ for origin, values in config_origin_generator(config['origins'], apiurl,
project, package, True):
+ if entity_exists(apiurl, origin, package):
+ return origin
+
+ return None
+
+def policy_evaluate(apiurl, project, package,
+ origin_info_new, origin_info_old,
+ source_hash_new, source_hash_old):
+ if origin_info_new is None:
+ config = config_load(apiurl, project)
+ origins = config_origin_list(config, apiurl, project, package, True)
+ comment = 'Source not found in allowed origins:\n\n- {}'.format('\n-
'.join(origins))
+ return PolicyResult(config['unknown_origin_wait'], False, {},
[comment])
+
+ policy = policy_get(apiurl, project, package, origin_info_new.project)
+ inputs = policy_input_calculate(apiurl, project, package,
+ origin_info_new, origin_info_old,
+ source_hash_new, source_hash_old)
+ result = policy_input_evaluate(policy, inputs)
+
+ inputs['pending_submission'] = str(inputs['pending_submission'])
+ logging.debug('policy_evaluate:\n\n{}'.format('\n'.join([
+ '# policy\n{}'.format(yaml.dump(policy, default_flow_style=False)),
+ '# inputs\n{}'.format(yaml.dump(inputs, default_flow_style=False)),
+ str(result)])))
+ return result
+
+@memoize(session=True)
+def policy_get(apiurl, project, package, origin):
+ config = config_load(apiurl, project)
+ for key, values in config_origin_generator(config['origins'], apiurl,
project, package):
+ if key == origin:
+ return policy_get_preprocess(apiurl, origin, values)
+
+ return None
+
+def policy_get_preprocess(apiurl, origin, policy):
+ project = origin.rstrip('~')
+ config_project = Config.get(apiurl, project)
+ policy['pending_submission_allowed_reviews'] = filter(None, [
+ config_resolve_variable(v, config_project, 'config_source')
+ for v in policy['pending_submission_allowed_reviews']])
+
+ return policy
+
+def policy_input_calculate(apiurl, project, package,
+ origin_info_new, origin_info_old,
+ source_hash_new, source_hash_old):
+ inputs = {
+ # Treat no older origin info as new package.
+ 'new_package': not entity_exists(apiurl, project, package) or
origin_info_old is None,
+ 'pending_submission': origin_info_new.pending,
+ }
+
+ if inputs['new_package']:
+ origin_highest = origin_find_highest(apiurl, project, package)
+ inputs['from_highest_priority'] = \
+ origin_highest is None or origin_info_new.project == origin_highest
+ else:
+ workaround_new = origin_workaround_check(origin_info_new.project)
+ inputs['origin_change'] = origin_info_new.project !=
origin_info_old.project
+ if inputs['origin_change']:
+ config = config_load(apiurl, project)
+ origins = config_origin_list(config, apiurl, project, package)
+
+ inputs['higher_priority'] = \
+ origins.index(origin_info_new.project) <
origins.index(origin_info_old.project)
+ if workaround_new:
+ inputs['same_family'] = True
+ else:
+ inputs['same_family'] = \
+ origin_info_new.project in project_list_family(
+ apiurl, origin_info_old.project.rstrip('~'), True)
+ else:
+ inputs['higher_priority'] = None
+ inputs['same_family'] = True
+
+ if inputs['pending_submission']:
+ inputs['direction'] = 'forward'
+ else:
+ if workaround_new:
+ source_hashes = []
+ else:
+ source_hashes = list(package_source_hash_history(
+ apiurl, origin_info_new.project, package, 10, True))
+
+ try:
+ index_new = source_hashes.index(source_hash_new)
+ index_old = source_hashes.index(source_hash_old)
+ if index_new == index_old:
+ inputs['direction'] = 'none'
+ else:
+ inputs['direction'] = 'forward' if index_new < index_old
else 'backward'
+ except ValueError:
+ inputs['direction'] = 'unkown'
+
+ return inputs
+
+def policy_input_evaluate(policy, inputs):
+ result = PolicyResult(False, True, {}, [])
+
+ if inputs['new_package']:
+ if policy['maintainer_review_initial']:
+ result.reviews['maintainer'] = 'Need package maintainer approval
for inital submission.'
+
+ if not inputs['from_highest_priority']:
+ result.reviews['fallback'] = 'Not from the highest priority origin
which provides the package.'
+ else:
+ if inputs['direction'] == 'none':
+ return PolicyResult(False, False, {}, ['Identical source.'])
+
+ if inputs['origin_change']:
+ if inputs['higher_priority']:
+ if not inputs['same_family'] and inputs['direction'] !=
'forward':
+ result.reviews['fallback'] = 'Changing to a higher
priority origin, ' \
+ 'but from another family and {}
direction.'.format(inputs['direction'])
+ elif not inputs['same_family']:
+ result.reviews['fallback'] = 'Changing to a higher
priority origin, but from another family.'
+ elif inputs['direction'] != 'forward':
+ result.reviews['fallback'] = \
+ 'Changing to a higher priority origin, but {}
direction.'.format(inputs['direction'])
+ else:
+ result.reviews['fallback'] = 'Changing to a lower priority
origin.'
+ else:
+ if inputs['direction'] == 'forward':
+ if not policy['automatic_updates']:
+ result.reviews['fallback'] = 'Forward direction, but
automatic updates not allowed.'
+ else:
+ result.reviews['fallback'] = '{}
direction.'.format(inputs['direction'])
+
+ if inputs['pending_submission'] is not False:
+ reviews_not_allowed =
policy_input_evaluate_reviews_not_allowed(policy, inputs)
+ wait = not policy['pending_submission_allow'] or
len(reviews_not_allowed)
+ result = PolicyResult(wait, True, result.reviews, result.comments)
+
+ if wait:
+ if policy['pending_submission_allow'] and len(reviews_not_allowed):
+ result.comments.append('Waiting on reviews of {}:\n\n-
{}'.format(
+ inputs['pending_submission'].identifier, '\n-
'.join(reviews_not_allowed)))
+ else:
+ result.comments.append('Waiting on
{}.'.format(inputs['pending_submission'].identifier))
+
+ if policy['maintainer_review_always']:
+ # Placed last to override initial maintainer approval message.
+ result.reviews['maintainer'] = 'Need package maintainer approval.'
+
+ for additional_review in policy['additional_reviews']:
+ result.reviews[additional_review] = 'Additional review required based
on origin.'
+
+ return result
+
+def policy_input_evaluate_reviews_not_allowed(policy, inputs):
+ reviews_not_allowed = []
+ for review_remaining in inputs['pending_submission'].reviews_remaining:
+ allowed = False
+ for review_allowed in policy['pending_submission_allowed_reviews']:
+ if review_allowed.endswith('*') and
review_remaining.startswith(review_allowed[:-1]):
+ allowed = True
+ break
+ if review_remaining == review_allowed:
+ allowed = True
+ break
+
+ if not allowed:
+ reviews_not_allowed.append(review_remaining)
+
+ return reviews_not_allowed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/osclib/stagingapi.py
new/openSUSE-release-tools-20190221.324b92a/osclib/stagingapi.py
--- old/openSUSE-release-tools-20190213.be751cc/osclib/stagingapi.py
2019-02-13 08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/osclib/stagingapi.py
2019-02-21 19:08:06.000000000 +0100
@@ -47,6 +47,7 @@
from osclib.cache import Cache
from osclib.core import devel_project_get
+from osclib.core import entity_exists
from osclib.core import project_list_prefix
from osclib.core import project_pseudometa_file_load
from osclib.core import project_pseudometa_file_save
@@ -1406,15 +1407,7 @@
:param project: project name to check
:param package: optional package to check
"""
- if package:
- url = self.makeurl(['source', project, package, '_meta'])
- else:
- url = self.makeurl(['source', project, '_meta'])
- try:
- http_GET(url)
- except HTTPError:
- return False
- return True
+ return entity_exists(self.apiurl, project, package)
def package_version(self, project, package):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/osclib/util.py
new/openSUSE-release-tools-20190221.324b92a/osclib/util.py
--- old/openSUSE-release-tools-20190213.be751cc/osclib/util.py 2019-02-13
08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/osclib/util.py 2019-02-21
19:08:06.000000000 +0100
@@ -1,37 +1,55 @@
from osc import conf
from osclib.core import project_list_prefix
+from osclib.memoize import memoize
-def project_list_family(apiurl, project):
+@memoize(session=True)
+def project_list_family(apiurl, project, include_update=False):
"""
Determine the available projects within the same product family.
Skips < SLE-12 due to format change.
"""
+ if project.endswith(':NonFree'):
+ project = project[:-8]
+ project_suffix = ':NonFree'
+ else:
+ project_suffix = ''
+
if project == 'openSUSE:Factory':
- return [project]
+ return [project + project_suffix]
if project.endswith(':ARM') or project.endswith(':PowerPC'):
- return [project]
+ return [project + project_suffix]
count_original = project.count(':')
if project.startswith('SUSE:SLE'):
project = ':'.join(project.split(':')[:2])
- family_filter = lambda p: p.count(':') == count_original and
p.endswith(':GA')
+ family_filter = lambda p: p.count(':') == count_original and (
+ p.endswith(':GA') or (include_update and p.endswith(':Update')))
else:
- family_filter = lambda p: p.count(':') == count_original
+ family_filter = lambda p: p.count(':') == count_original or (
+ include_update and p.count(':') == count_original + 1 and
p.endswith(':Update'))
prefix = ':'.join(project.split(':')[:-1])
projects = project_list_prefix(apiurl, prefix)
+ projects = filter(family_filter, projects)
+
+ if project_suffix:
+ for i, project in enumerate(projects):
+ if project.endswith(':Update'):
+ projects[i] = project.replace(':Update', project_suffix +
':Update')
+ else:
+ projects[i] += project_suffix
- return filter(family_filter, projects)
+ return projects
-def project_list_family_prior(apiurl, project, include_self=False, last=None):
+def project_list_family_prior(apiurl, project, include_self=False, last=None,
include_update=False):
"""
Determine the available projects within the same product family released
prior to the specified project.
"""
- projects = project_list_family(apiurl, project)
+ projects = project_list_family(apiurl, project, include_update)
past = False
prior = []
for entry in sorted(projects, key=project_list_family_sorter,
reverse=True):
@@ -48,6 +66,25 @@
return prior
+def project_list_family_prior_pattern(apiurl, project_pattern, project=None,
include_update=True):
+ project_prefix, project_suffix = project_pattern.split('*', 2)
+ if project:
+ project = project if project.startswith(project_prefix) else None
+
+ if project:
+ projects = project_list_family_prior(apiurl, project,
include_update=include_update)
+ else:
+ if ':Leap:' in project_prefix:
+ project = project_prefix
+
+ if ':SLE-' in project_prefix:
+ project = project_prefix + ':GA'
+
+ projects = project_list_family(apiurl, project, include_update)
+ projects = sorted(projects, key=project_list_family_sorter,
reverse=True)
+
+ return [p for p in projects if p.startswith(project_prefix)]
+
def project_list_family_sorter(project):
"""Extract key to be used as sorter (oldest to newest)."""
version = project_version(project)
@@ -82,7 +119,7 @@
version += float(parts[2][2:]) / 10
return version
- return None
+ return 0
def mail_send(project, to, subject, body, from_key='maintainer',
followup_to_key='release-list', dry=False):
from email.mime.text import MIMEText
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/staging-report.py
new/openSUSE-release-tools-20190221.324b92a/staging-report.py
--- old/openSUSE-release-tools-20190213.be751cc/staging-report.py
2019-02-13 08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/staging-report.py
2019-02-21 19:08:06.000000000 +0100
@@ -142,10 +142,11 @@
help='project to check (ex. openSUSE:Factory,
openSUSE:Leap:15.1)')
parser.add_argument('-d', '--debug', action='store_true', default=False,
help='enable debug information')
+ parser.add_argument('-A', '--apiurl', metavar='URL', help='API URL')
args = parser.parse_args()
- osc.conf.get_config()
+ osc.conf.get_config(override_apiurl=args.apiurl)
osc.conf.config['debug'] = args.debug
apiurl = osc.conf.config['apiurl']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/systemd/osrt-origin-manager.service
new/openSUSE-release-tools-20190221.324b92a/systemd/osrt-origin-manager.service
---
old/openSUSE-release-tools-20190213.be751cc/systemd/osrt-origin-manager.service
1970-01-01 01:00:00.000000000 +0100
+++
new/openSUSE-release-tools-20190221.324b92a/systemd/osrt-origin-manager.service
2019-02-21 19:08:06.000000000 +0100
@@ -0,0 +1,11 @@
+[Unit]
+Description=openSUSE Release Tools: origin-manager
+
+[Service]
+User=osrt-origin-manager
+SyslogIdentifier=osrt-origin-manager
+ExecStart=/usr/bin/osrt-origin-manager --debug review
+RuntimeMaxSec=3 hour
+
+[Install]
+WantedBy=multi-user.target
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/systemd/osrt-origin-manager.timer
new/openSUSE-release-tools-20190221.324b92a/systemd/osrt-origin-manager.timer
---
old/openSUSE-release-tools-20190213.be751cc/systemd/osrt-origin-manager.timer
1970-01-01 01:00:00.000000000 +0100
+++
new/openSUSE-release-tools-20190221.324b92a/systemd/osrt-origin-manager.timer
2019-02-21 19:08:06.000000000 +0100
@@ -0,0 +1,10 @@
+[Unit]
+Description=openSUSE Release Tools: origin-manager
+
+[Timer]
+OnBootSec=120
+OnUnitInactiveSec=5 min
+Unit=osrt-origin-manager.service
+
+[Install]
+WantedBy=timers.target
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/openSUSE-release-tools-20190213.be751cc/tests/obs.py
new/openSUSE-release-tools-20190221.324b92a/tests/obs.py
--- old/openSUSE-release-tools-20190213.be751cc/tests/obs.py 2019-02-13
08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/tests/obs.py 2019-02-21
19:08:06.000000000 +0100
@@ -827,6 +827,10 @@
# /search/
#
+ @GET('/search/project')
+ def search_project(self, request, uri, headers):
+ return (200, headers, '<collection matches="0"></collection>')
+
@GET('/search/project/id')
def search_project_id(self, request, uri, headers):
"""Return a search result /search/project/id."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/openSUSE-release-tools-20190213.be751cc/totest-manager.py
new/openSUSE-release-tools-20190221.324b92a/totest-manager.py
--- old/openSUSE-release-tools-20190213.be751cc/totest-manager.py
2019-02-13 08:50:17.000000000 +0100
+++ new/openSUSE-release-tools-20190221.324b92a/totest-manager.py
2019-02-21 19:08:06.000000000 +0100
@@ -770,7 +770,9 @@
ImageProduct('livecd-tumbleweed-gnome', ['i586',
'x86_64']),
ImageProduct('livecd-tumbleweed-x11', ['i586',
'x86_64'])]
- container_products = [ImageProduct('opensuse-tumbleweed-image:docker',
['i586', 'x86_64'])]
+ container_products = [ImageProduct('opensuse-tumbleweed-image:docker',
['i586', 'x86_64']),
+ ImageProduct('kubic-kured-image', ['x86_64']),
+ ImageProduct('kubic-pause-image', ['i586',
'x86_64'])]
image_products = [
ImageProduct('opensuse-tumbleweed-image:lxc', ['i586', 'x86_64']),
@@ -802,7 +804,9 @@
image_products = [ImageProduct('opensuse-tumbleweed-image:lxc',
['ppc64le'])]
- container_products = [ImageProduct('opensuse-tumbleweed-image:docker',
['ppc64le'])]
+ container_products = [ImageProduct('opensuse-tumbleweed-image:docker',
['ppc64le']),
+ ImageProduct('kubic-kured-image', ['ppc64le']),
+ ImageProduct('kubic-pause-image', ['ppc64le'])]
def __init__(self, *args, **kwargs):
ToTestBase.__init__(self, *args, **kwargs)
@@ -855,7 +859,9 @@
image_products = [ImageProduct('opensuse-tumbleweed-image:lxc', ['armv6l',
'armv7l', 'aarch64'])]
- container_products = [ImageProduct('opensuse-tumbleweed-image:docker',
['aarch64'])]
+ container_products = [ImageProduct('opensuse-tumbleweed-image:docker',
['aarch64']),
+ ImageProduct('kubic-kured-image', ['aarch64']),
+ ImageProduct('kubic-pause-image', ['aarch64'])]
# JeOS doesn't follow build numbers of main isos
need_same_build_number = False
++++++ openSUSE-release-tools.obsinfo ++++++
--- /var/tmp/diff_new_pack.L1hfoj/_old 2019-02-24 17:19:39.224405093 +0100
+++ /var/tmp/diff_new_pack.L1hfoj/_new 2019-02-24 17:19:39.228405092 +0100
@@ -1,5 +1,5 @@
name: openSUSE-release-tools
-version: 20190213.be751cc
-mtime: 1550044217
-commit: be751cc578fb38e713421370ea253d4055becdb0
+version: 20190221.324b92a
+mtime: 1550772486
+commit: 324b92a0a5a9f5d30584e670ee1a082d830a71da