Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package openSUSE-release-tools for openSUSE:Factory checked in at 2022-03-23 20:19:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openSUSE-release-tools (Old) and /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.25692 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openSUSE-release-tools" Wed Mar 23 20:19:17 2022 rev:408 rq:964323 version:20220323.bd336586 Changes: -------- --- /work/SRC/openSUSE:Factory/openSUSE-release-tools/openSUSE-release-tools.changes 2022-03-22 19:40:05.051082607 +0100 +++ /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.25692/openSUSE-release-tools.changes 2022-03-23 20:21:09.626557197 +0100 @@ -1,0 +2,27 @@ +Wed Mar 23 15:42:45 UTC 2022 - opensuse-releaset...@opensuse.org + +- Update to version 20220323.bd336586: + * Remove rpmlint check from check_source + * Small fixes for staging-installcheck + * Add osc-pcheck: support devel prj maintainers + +------------------------------------------------------------------- +Wed Mar 23 14:16:39 UTC 2022 - Stephan Kulow <co...@suse.com> + +- just testing + +------------------------------------------------------------------- +Wed Mar 23 10:35:13 UTC 2022 - opensuse-releaset...@opensuse.org + +- Update to version 20220323.ad923e6f: + * Add some documentation for pkglistgen staging workflow + * accept: Create pkglistgen .changes entry and commit + * Prepare applying pkglistgen diffs on staging accept + +------------------------------------------------------------------- +Tue Mar 22 14:29:18 UTC 2022 - opensuse-releaset...@opensuse.org + +- Update to version 20220322.e78b5352: + * staging-installcheck: Exit 1 for the letter stagings + +------------------------------------------------------------------- Old: ---- openSUSE-release-tools-20220321.48333852.obscpio New: ---- openSUSE-release-tools-20220323.bd336586.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openSUSE-release-tools.spec ++++++ --- /var/tmp/diff_new_pack.PVnDVa/_old 2022-03-23 20:21:10.358557612 +0100 +++ /var/tmp/diff_new_pack.PVnDVa/_new 2022-03-23 20:21:10.362557614 +0100 @@ -20,7 +20,7 @@ %define source_dir openSUSE-release-tools %define announcer_filename factory-package-news Name: openSUSE-release-tools -Version: 20220321.48333852 +Version: 20220323.bd336586 Release: 0 Summary: Tools to aid in staging and release work for openSUSE/SUSE License: GPL-2.0-or-later AND MIT @@ -242,6 +242,18 @@ %description -n osc-plugin-cycle OSC plugin for cycle visualization, see `osc cycle --help`. +%package -n osc-plugin-pcheck +Summary: OSC plugin to support devel project maintainers +Group: Development/Tools/Other +Requires: osc >= 0.165.1 +Requires: osclib = %{version} +BuildArch: noarch + +%description -n osc-plugin-pcheck +OSC plugin for devel project maintainers. Helps them check the submit +state (done, todo, missing links) of a devel project to the parent project. +See 'osc pcheck --help' + %package -n osc-plugin-origin Summary: OSC plugin for origin management Group: Development/Tools/Other @@ -378,6 +390,7 @@ %exclude %{_datadir}/%{source_dir}/osclib %exclude %{_datadir}/%{source_dir}/osc-cycle.py %exclude %{_datadir}/%{source_dir}/osc-origin.py +%exclude %{_datadir}/%{source_dir}/osc-pcheck.py %exclude %{_datadir}/%{source_dir}/osc-staging.py %exclude %{_datadir}/%{source_dir}/findfileconflicts %exclude %{_datadir}/%{source_dir}/write_repo_susetags_file.pl @@ -470,6 +483,10 @@ %{_datadir}/%{source_dir}/osc-cycle.py %{osc_plugin_dir}/osc-cycle.py +%files -n osc-plugin-pcheck +%{_datadir}/%{source_dir}/osc-pcheck.py +%{osc_plugin_dir}/osc-pcheck.py + %files -n osc-plugin-origin %{_datadir}/%{source_dir}/osc-origin.py %{osc_plugin_dir}/osc-origin.py ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.PVnDVa/_old 2022-03-23 20:21:10.406557639 +0100 +++ /var/tmp/diff_new_pack.PVnDVa/_new 2022-03-23 20:21:10.410557641 +0100 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/openSUSE/openSUSE-release-tools.git</param> - <param name="changesrevision">1a26c0202014ca37682cffdf9125050e00f8b927</param> + <param name="changesrevision">bd3365863f090bfe86f03dd73b483431c9ed33f3</param> </service> </servicedata> ++++++ openSUSE-release-tools-20220321.48333852.obscpio -> openSUSE-release-tools-20220323.bd336586.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/CONTENTS.md new/openSUSE-release-tools-20220323.bd336586/CONTENTS.md --- old/openSUSE-release-tools-20220321.48333852/CONTENTS.md 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/CONTENTS.md 2022-03-23 16:41:56.000000000 +0100 @@ -286,6 +286,12 @@ * Package: -- * Usage: used to debug problems. See https://github.com/openSUSE/openSUSE-release-tools/pull/992 as an example. +#### osc-pcheck.py +* Sources: [osc-pcheck.py](osc-pcheck.py) +* Documentation: -- +* Package: -- +* Usage: Overview for devel project maintainers: unsubmitted packages with diff, submitted packages, and unlinked packages (things to monitor) + #### compare_pkglist.py Compares packages status between two projects. It determines which project has the newer version of a package, @@ -338,4 +344,4 @@ * Sources: [check_bugowner.py](check_bugowner.py) * Documentation: -- * Package: openSUSE-release-tools - * Usage: gocd \ No newline at end of file + * Usage: gocd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/check_source.py new/openSUSE-release-tools-20220323.bd336586/check_source.py --- old/openSUSE-release-tools-20220321.48333852/check_source.py 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/check_source.py 2022-03-23 16:41:56.000000000 +0100 @@ -16,8 +16,6 @@ from osclib.core import devel_project_fallback from osclib.core import group_members from osclib.core import package_kind -from osclib.core import source_file_load -from osclib.core import target_archs from osclib.core import create_add_role_request from osc.core import show_project_meta from osc.core import get_request_list @@ -30,11 +28,6 @@ class CheckSource(ReviewBot.ReviewBot): SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) - AUDIT_BUG_URL = "https://en.opensuse.org/openSUSE:Package_security_guidelines#audit_bugs" - AUDIT_BUG_MESSAGE = """The package is submitted to an official product and it has warnings that indicate -that it need to go through a security review. -Those warnings can only be ignored in devel projects. For more information please read: {}.""".format( - AUDIT_BUG_URL) def __init__(self, *args, **kwargs): ReviewBot.ReviewBot.__init__(self, *args, **kwargs) @@ -59,8 +52,6 @@ self.required_maintainer = config.get('required-source-maintainer', '') self.devel_whitelist = config.get('devel-whitelist', '').split() self.skip_add_reviews = False - self.security_review_team = config.get('security-review-team', 'security-team') - self.bad_rpmlint_entries = config.get('bad-rpmlint-entries', '').split() if self.action.type == 'maintenance_incident': # The workflow effectively enforces the names to match and the @@ -265,68 +256,8 @@ elif self.repo_checker is not None: self.add_review(self.request, by_user=self.repo_checker, msg='Please review build success') - if self.bad_rpmlint_entries: - warnings = self.has_whitelist_warnings(source_project, source_package, target_project, target_package) - if warnings: - # if there are any add a review for the security team - # maybe add the found warnings to the message for the review - message = CheckSource.AUDIT_BUG_MESSAGE + \ - "\nTriggered by whitelist warnings:\n{}".format("\n".join(warnings)) - self.add_review(self.request, by_group=self.security_review_team, msg=message) - warnings = self.suppresses_whitelist_warnings(source_project, source_package) - if warnings: - message = CheckSource.AUDIT_BUG_MESSAGE + \ - "\nTriggered by suppressed whitelist warning:\n{}".format("\n".join(warnings)) - self.add_review(self.request, by_group=self.security_review_team, msg=message) - return True - def suppresses_whitelist_warnings(self, source_project, source_package): - # checks if there's a rpmlintrc that suppresses warnings that we check - found_entries = set() - contents = source_file_load(self.apiurl, source_project, source_package, source_package + '-rpmlintrc') - if contents: - contents = re.sub(r'(?m)^ *#.*\n?', '', contents) - matches = re.findall(r'addFilter\(["\']([^"\']+)["\']\)', contents) - # this is a bit tricky. Since users can specify arbitrary regular expresions it's not easy - # to match bad_rpmlint_entries against what we found - for entry in self.bad_rpmlint_entries: - for match in matches: - # First we try to see if our entries appear verbatim in the rpmlint entries - if entry in match: - self.logger.info(f'found suppressed whitelist warning: {match}') - found_entries.add(match) - # if that's not the case then we check if one of the entries in the rpmlint file would match one - # of our entries (e.g. addFilter(".*") - elif re.search(match, entry) and match not in found_entries: - self.logger.info(f'found rpmlint entry that suppresses an important warning: {match}') - found_entries.add(match) - - return found_entries - - def has_whitelist_warnings(self, source_project, source_package, target_project, target_package): - # this checks if this is a submit to an product project and it has warnings for non-whitelisted permissions/files - found_entries = set() - url = osc.core.makeurl(self.apiurl, ['build', target_project]) - xml = ET.parse(osc.core.http_GET(url)).getroot() - for f in xml.findall('entry'): - # we check all repos in the source project for errors that exist in the target project - repo = f.attrib['name'] - query = {'last': 1, } - for arch in target_archs(self.apiurl, source_project, repo): - url = osc.core.makeurl(self.apiurl, ['build', source_project, repo, - arch, source_package, '_log'], query=query) - try: - result = osc.core.http_GET(url) - contents = str(result.read()) - for entry in self.bad_rpmlint_entries: - if (': W: ' + entry in contents) and not (entry in found_entries): - self.logger.info(f'found missing whitelist for warning: {entry}') - found_entries.add(entry) - except HTTPError as e: - self.logger.info('ERROR in URL %s [%s]' % (url, e)) - return found_entries - def is_devel_project(self, source_project, target_project): if source_project in self.devel_whitelist: return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/dist/package/openSUSE-release-tools.spec new/openSUSE-release-tools-20220323.bd336586/dist/package/openSUSE-release-tools.spec --- old/openSUSE-release-tools-20220321.48333852/dist/package/openSUSE-release-tools.spec 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/dist/package/openSUSE-release-tools.spec 2022-03-23 16:41:56.000000000 +0100 @@ -242,6 +242,18 @@ %description -n osc-plugin-cycle OSC plugin for cycle visualization, see `osc cycle --help`. +%package -n osc-plugin-pcheck +Summary: OSC plugin to support devel project maintainers +Group: Development/Tools/Other +Requires: osc >= 0.165.1 +Requires: osclib = %{version} +BuildArch: noarch + +%description -n osc-plugin-pcheck +OSC plugin for devel project maintainers. Helps them check the submit +state (done, todo, missing links) of a devel project to the parent project. +See 'osc pcheck --help' + %package -n osc-plugin-origin Summary: OSC plugin for origin management Group: Development/Tools/Other @@ -378,6 +390,7 @@ %exclude %{_datadir}/%{source_dir}/osclib %exclude %{_datadir}/%{source_dir}/osc-cycle.py %exclude %{_datadir}/%{source_dir}/osc-origin.py +%exclude %{_datadir}/%{source_dir}/osc-pcheck.py %exclude %{_datadir}/%{source_dir}/osc-staging.py %exclude %{_datadir}/%{source_dir}/findfileconflicts %exclude %{_datadir}/%{source_dir}/write_repo_susetags_file.pl @@ -470,6 +483,10 @@ %{_datadir}/%{source_dir}/osc-cycle.py %{osc_plugin_dir}/osc-cycle.py +%files -n osc-plugin-pcheck +%{_datadir}/%{source_dir}/osc-pcheck.py +%{osc_plugin_dir}/osc-pcheck.py + %files -n osc-plugin-origin %{_datadir}/%{source_dir}/osc-origin.py %{osc_plugin_dir}/osc-origin.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/docs/pkglistgen.md new/openSUSE-release-tools-20220323.bd336586/docs/pkglistgen.md --- old/openSUSE-release-tools-20220321.48333852/docs/pkglistgen.md 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/docs/pkglistgen.md 2022-03-23 16:41:56.000000000 +0100 @@ -12,7 +12,7 @@ The package list generator reads several files. The most important are group*.yml (tradionally only groups.yml) within 000package-groups. ### supportstatus.txt - TODO +The file lists the packages and their support level. It's only necessary to list packages here that have a different level than the default level specificied in the groups. The format is plain text: <package name> <level> - the level is handed over 1:1 to KIWI file. Currently used values are: unsupported, l2 and l3 ### group*.yml The file is a list of package lists and the special hash 'OUTPUT'. OUTPUT contains an entry for every group file that needs to be written out. The group name of it needs to exist as package list as well. OUTPUT also contains flags for the groups. @@ -90,3 +90,10 @@ ## Overlap calculcation TODO +## Handling in staging workflow +If 000package-groups contains a file named summary-staging.txt, the bot will trigger a diff mode on staging projects. +It will create an equal summary-staging.txt in 000product and create a comment with a human readable diff in the staging +project. This comment can be replied to. If the reply starts with 'approve', staging accept will apply the diff to this +txt file. If the reply starts with 'ignore', the bot will continue with the pipeline and do nothing on staging accept. + +This way simple changes to the summary can be accepted without a submit request (of which there can only be one at a time). \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/osc-pcheck.py new/openSUSE-release-tools-20220323.bd336586/osc-pcheck.py --- old/openSUSE-release-tools-20220321.48333852/osc-pcheck.py 1970-01-01 01:00:00.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/osc-pcheck.py 2022-03-23 16:41:56.000000000 +0100 @@ -0,0 +1,120 @@ +# Copyright (C) 2015 SUSE Linux Products GmbH +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty 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. + +from osc import cmdln +import osc.core + + +@cmdln.option('--push', action='store_true', + help="Push changed packages to their parents") +@cmdln.option('-m', "--message", + help='Specify submit message') +def do_pcheck(self, subcmd, opts, project): + """${cmd_name}: Show changed packages (packages that have a diff) + + Examples: + osc pcheck <prj> # shows changed packages etc. for <prj> + + --push Create submit requests for packages with a diff (if none exists yet) + -m Specify submit message (defaut: "Scripted push of project <prj>") + + """ + apiurl = self.get_api_url() + sinfos = osc.core.get_project_sourceinfo(apiurl, project, True) + todo = {} + errors = {} + md5s = {} + pmap = {} + changed = [] + changeSRed = {} + api = oscapi(apiurl) + for pkg, sinfo in sinfos.items(): + if sinfo.find('error'): + errors[pkg] = sinfo.find('error').text + continue + elif sinfo.find('originpackage') is not None: + # This is a package created from a _multibuild + # Status will be checked by the main one (which + # has no originpackage.) so let's not continue further + continue + elif sinfo.find('linked') is not None: + elm = sinfo.find('linked') + key = '%s/%s' % (elm.get('project'), elm.get('package')) + pmap.setdefault(key, []).append(pkg) + todo.setdefault(elm.get('project'), []).append(elm.get('package')) + md5s[pkg] = sinfo.get('verifymd5') + for prj, pkgs in todo.items(): + sinfos = osc.core.get_project_sourceinfo(apiurl, prj, True, *pkgs) + for pkg, sinfo in sinfos.items(): + key = '%s/%s' % (prj, pkg) + for p in pmap[key]: + vmd5 = md5s.pop(p) + if vmd5 == sinfo.get('verifymd5'): + continue + # Is there already an SR outgoing for this package? + SRid = int(api.sr_for_package(project, p)) + if SRid > 0: + changeSRed[p] = SRid + else: + changed.append(p) + if opts.push: + if opts.message: + message = opts.message + else: + message = "Scripted push from {project}".format(project=project) + api.create(project=project, package=p, target=prj, message=message) + + overview = 'Overview of project {}'.format(project) + print() + print(overview) + print('=' * len(overview)) + print('Changed & unsubmitted packages: %d' % len(changed)) + print(', '.join(changed)) + print() + print('Changed & submitted packages: %d' % len(changeSRed.keys())) + print(', '.join(['%s(%s)' % (pkg, SR) for pkg, SR in changeSRed.items()])) + print() + print('Packages without link: %d' % len(md5s.keys())) + print(', '.join(md5s.keys())) + print() + print('Packages with errors: %d' % len(errors.keys())) + print('\n'.join(['%s: %s' % (p, err) for p, err in errors.items()])) + + +class oscapi: + def __init__(self, apiurl): + self.apiurl = apiurl + + def sr_for_package(self, project, package): + query = "(state/@name='new' or state/@name='review') and " \ + "(action/source/@project='{project}' or submit/source/@project='{project}') and " \ + "(action/source/@package='{package}' or submit/source/@package='Packafe')".format(project=project, package=package) + result = osc.core.search(self.apiurl, request=query) + collection = result['request'] + for root in collection.findall('request'): + return root.get('id') + return 0 + + def create(self, project, package, target, message): + currev = osc.core.get_source_rev(self.apiurl, project, package)['rev'] + print("Creating a request from {project}/{package}".format(project=project, package=package)) + query = {'cmd': 'create'} + url = osc.core.makeurl(self.apiurl, ['request'], query=query) + + data = '<request type="submit"><submit><source project="{project}" package="{package}" rev="{rev}"/>' \ + '<target project="{target}" package="{package}"/></submit><state name="new"/><description>{message}</description>' \ + '</request>'.format(project=project, package=package, target=target, rev=currev, message=message) + osc.core.http_POST(url, data=data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/osclib/accept_command.py new/openSUSE-release-tools-20220323.bd336586/osclib/accept_command.py --- old/openSUSE-release-tools-20220321.48333852/osclib/accept_command.py 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/osclib/accept_command.py 2022-03-23 16:41:56.000000000 +0100 @@ -12,6 +12,7 @@ from osclib.core import attribute_value_load from osclib.core import source_file_load from osclib.core import source_file_save +from osclib.pkglistgen_comments import PkglistComments from datetime import date @@ -19,6 +20,7 @@ def __init__(self, api): self.api = api self.config = conf.config[self.api.project] + self.pkglist_comments = PkglistComments(self.api.apiurl) def find_new_requests(self, project): match = f"state/@name='new' and action/target/@project='{project}'" @@ -133,6 +135,7 @@ self.api.staging_deactivate(project) + self.pkglist_comments.check_staging_accept(project, self.api.project) self.reset_rebuild_data(project) if cleanup: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/osclib/core.py new/openSUSE-release-tools-20220323.bd336586/osclib/core.py --- old/openSUSE-release-tools-20220321.48333852/osclib/core.py 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/osclib/core.py 2022-03-23 16:41:56.000000000 +0100 @@ -127,8 +127,11 @@ return users -def package_list(apiurl, project): - url = makeurl(apiurl, ['source', project], {'expand': 1}) +def package_list(apiurl, project, expand=True): + query = {} + if expand: + query['expand'] = 1 + url = makeurl(apiurl, ['source', project], query) root = ET.parse(http_GET(url)).getroot() packages = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/osclib/pkglistgen_comments.py new/openSUSE-release-tools-20220323.bd336586/osclib/pkglistgen_comments.py --- old/openSUSE-release-tools-20220321.48333852/osclib/pkglistgen_comments.py 1970-01-01 01:00:00.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/osclib/pkglistgen_comments.py 2022-03-23 16:41:56.000000000 +0100 @@ -0,0 +1,277 @@ +import datetime +import textwrap +import re +import tempfile +import logging +import os +import sys +from lxml import etree as ET + +from osclib.comments import CommentAPI +from osc.core import checkout_package, http_GET, makeurl +from osc.core import Package + +MARKER = 'PackageListDiff' + + +class PkglistComments(object): + """Handling staging comments of diffs""" + + def __init__(self, apiurl): + self.apiurl = apiurl + self.comment = CommentAPI(apiurl) + + def read_summary_file(self, file): + ret = dict() + with open(file, 'r') as f: + for line in f: + pkg, group = line.strip().split(':') + ret.setdefault(pkg, []) + ret[pkg].append(group) + return ret + + def write_summary_file(self, file, content): + output = [] + for pkg in sorted(content): + for group in sorted(content[pkg]): + output.append(f"{pkg}:{group}") + + with open(file, 'w') as f: + for line in sorted(output): + f.write(line + '\n') + + def calculcate_package_diff(self, old_file, new_file): + old_file = self.read_summary_file(old_file) + new_file = self.read_summary_file(new_file) + + # remove common part + keys = list(old_file.keys()) + for key in keys: + if new_file.get(key, []) == old_file[key]: + del new_file[key] + del old_file[key] + + if not old_file and not new_file: + return None + + removed = dict() + for pkg in old_file: + old_groups = old_file[pkg] + if new_file.get(pkg): + continue + removekey = ','.join(old_groups) + removed.setdefault(removekey, []) + removed[removekey].append(pkg) + + report = '' + for rm in sorted(removed.keys()): + report += f"**Remove from {rm}**\n\n```\n" + paragraph = ', '.join(removed[rm]) + report += "\n".join(textwrap.wrap(paragraph, width=90, break_long_words=False, break_on_hyphens=False)) + report += "\n```\n\n" + + moved = dict() + for pkg in old_file: + old_groups = old_file[pkg] + new_groups = new_file.get(pkg) + if not new_groups: + continue + movekey = ','.join(old_groups) + ' to ' + ','.join(new_groups) + moved.setdefault(movekey, []) + moved[movekey].append(pkg) + + for move in sorted(moved.keys()): + report += f"**Move from {move}**\n\n```\n" + paragraph = ', '.join(moved[move]) + report += "\n".join(textwrap.wrap(paragraph, width=90, break_long_words=False, break_on_hyphens=False)) + report += "\n```\n\n" + + added = dict() + for pkg in new_file: + if pkg in old_file: + continue + addkey = ','.join(new_file[pkg]) + added.setdefault(addkey, []) + added[addkey].append(pkg) + + for group in sorted(added): + report += f"**Add to {group}**\n\n```\n" + paragraph = ', '.join(added[group]) + report += "\n".join(textwrap.wrap(paragraph, width=90, break_long_words=False, break_on_hyphens=False)) + report += "\n```\n\n" + + return report.strip() + + def handle_package_diff(self, project, old_file, new_file): + comments = self.comment.get_comments(project_name=project) + comment, _ = self.comment.comment_find(comments, MARKER) + + report = self.calculcate_package_diff(old_file, new_file) + if not report: + if comment: + self.comment.delete(comment['id']) + return 0 + report = self.comment.add_marker(report, MARKER) + + if comment: + write_comment = report != comment['comment'] + else: + write_comment = True + if write_comment: + if comment: + self.comment.delete(comment['id']) + self.comment.add_comment(project_name=project, comment=report) + else: + for c in comments.values(): + if c['parent'] == comment['id']: + ct = c['comment'] + if ct.startswith('ignore ') or ct == 'ignore': + print(c) + return 0 + if ct.startswith('approve ') or ct == 'approve': + print(c) + return 0 + + return 1 + + def is_approved(self, comment, comments): + if not comment: + return None + + for c in comments.values(): + if c['parent'] == comment['id']: + ct = c['comment'] + if ct.startswith('approve ') or ct == 'approve': + return c['who'] + return None + + def parse_title(self, line): + m = re.match(r'\*\*Add to (.*)\*\*', line) + if m: + return {'cmd': 'add', 'to': m.group(1).split(','), 'pkgs': []} + m = re.match(r'\*\*Move from (.*) to (.*)\*\*', line) + if m: + return {'cmd': 'move', 'from': m.group(1).split(','), 'to': m.group(2).split(','), 'pkgs': []} + m = re.match(r'\*\*Remove from (.*)\*\*', line) + if m: + return {'cmd': 'remove', 'from': m.group(1).split(','), 'pkgs': []} + return None + + def parse_sections(self, comment): + current_section = None + sections = [] + in_quote = False + for line in comment.split('\n'): + if line.startswith('**'): + if current_section: + sections.append(current_section) + current_section = self.parse_title(line) + continue + if line.startswith("```"): + in_quote = not in_quote + continue + if in_quote: + for pkg in line.split(','): + pkg = pkg.strip() + if pkg: + current_section['pkgs'].append(pkg) + if current_section: + sections.append(current_section) + return sections + + def apply_move(self, content, section): + for pkg in section['pkgs']: + pkg_content = content[pkg] + for group in section['from']: + try: + pkg_content.remove(group) + except ValueError: + logging.error(f"Can't remove {pkg} from {group}, not there. Mismatch.") + sys.exit(1) + for group in section['to']: + pkg_content.append(group) + content[pkg] = pkg_content + + def apply_add(self, content, section): + for pkg in section['pkgs']: + content.setdefault(pkg, []) + content[pkg] += section['to'] + + def apply_remove(self, content, section): + for pkg in section['pkgs']: + pkg_content = content[pkg] + for group in section['from']: + try: + pkg_content.remove(group) + except ValueError: + logging.error(f"Can't remove {pkg} from {group}, not there. Mismatch.") + sys.exit(1) + content[pkg] = pkg_content + + def apply_commands(self, filename, sections): + content = self.read_summary_file(filename) + for section in sections: + if section['cmd'] == 'move': + self.apply_move(content, section) + elif section['cmd'] == 'add': + self.apply_add(content, section) + elif section['cmd'] == 'remove': + self.apply_remove(content, section) + self.write_summary_file(filename, content) + + def format_pkgs(self, pkgs): + text = ', '.join(pkgs) + return " " + "\n ".join(textwrap.wrap(text, width=68, break_long_words=False, break_on_hyphens=False)) + "\n\n" + + def format_move(self, section): + gfrom = ','.join(section['from']) + gto = ','.join(section['to']) + text = f" * Move from {gfrom} to {gto}:\n" + return text + self.format_pkgs(section['pkgs']) + + def format_add(self, section): + gto = ','.join(section['to']) + text = f" * Add to {gto}:\n" + return text + self.format_pkgs(section['pkgs']) + + def format_remove(self, section): + gfrom = ','.join(section['from']) + text = f" * Remove from {gfrom}:\n" + return text + self.format_pkgs(section['pkgs']) + + def apply_changes(self, filename, sections, approver): + text = "-------------------------------------------------------------------\n" + now = datetime.datetime.utcnow() + date = now.strftime("%a %b %d %H:%M:%S UTC %Y") + url = makeurl(self.apiurl, ['person', approver]) + root = ET.parse(http_GET(url)) + realname = root.find('realname').text + email = root.find('email').text + text += f"{date} - {realname} <{email}>\n\n- Approved changes to summary-staging.txt\n" + for section in sections: + if section['cmd'] == 'move': + text += self.format_move(section) + elif section['cmd'] == 'add': + text += self.format_add(section) + elif section['cmd'] == 'remove': + text += self.format_remove(section) + with open(filename + '.new', 'w') as writer: + writer.write(text) + with open(filename, 'r') as reader: + for line in reader: + writer.write(line) + os.rename(filename + '.new', filename) + + def check_staging_accept(self, project, target): + comments = self.comment.get_comments(project_name=project) + comment, _ = self.comment.comment_find(comments, MARKER) + approver = self.is_approved(comment, comments) + if not approver: + return + sections = self.parse_sections(comment['comment']) + with tempfile.TemporaryDirectory() as tmpdirname: + checkout_package(self.apiurl, target, '000package-groups', expand_link=True, outdir=tmpdirname) + self.apply_commands(tmpdirname + '/summary-staging.txt', sections) + self.apply_changes(tmpdirname + '/package-groups.changes', sections, approver) + package = Package(tmpdirname) + package.commit(msg='Approved packagelist changes', skip_local_service_run=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/pkglistgen/tool.py new/openSUSE-release-tools-20220323.bd336586/pkglistgen/tool.py --- old/openSUSE-release-tools-20220321.48333852/pkglistgen/tool.py 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/pkglistgen/tool.py 2022-03-23 16:41:56.000000000 +0100 @@ -7,7 +7,6 @@ import shutil import subprocess import yaml -import textwrap from lxml import etree as ET @@ -23,7 +22,7 @@ from osclib.core import repository_path_expand from osclib.core import repository_arch_state from osclib.cache_manager import CacheManager -from osclib.comments import CommentAPI +from osclib.pkglistgen_comments import PkglistComments from urllib.parse import urlparse @@ -33,7 +32,6 @@ SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) PRODUCT_SERVICE = '/usr/lib/obs/service/create_single_product' -MARKER = 'PackageListDiff' # share header cache with repochecker CACHEDIR = CacheManager.directory('repository-meta') @@ -48,7 +46,7 @@ def __init__(self): ToolBase.ToolBase.__init__(self) self.logger = logging.getLogger(__name__) - self.comment = CommentAPI(self.apiurl) + self.comment = PkglistComments(self.apiurl) self.reset() def reset(self): @@ -508,106 +506,6 @@ print('%endif', file=output) output.flush() - def read_summary_file(self, file): - ret = dict() - with open(file, 'r') as f: - for line in f: - pkg, group = line.strip().split(':') - ret.setdefault(pkg, []) - ret[pkg].append(group) - return ret - - def calculcate_package_diff(self, old_file, new_file): - old_file = self.read_summary_file(old_file) - new_file = self.read_summary_file(new_file) - - # remove common part - keys = list(old_file.keys()) - for key in keys: - if new_file.get(key, []) == old_file[key]: - del new_file[key] - del old_file[key] - - if not old_file and not new_file: - return None - - removed = dict() - for pkg in old_file: - old_groups = old_file[pkg] - if new_file.get(pkg): - continue - removekey = ','.join(old_groups) - removed.setdefault(removekey, []) - removed[removekey].append(pkg) - - report = '' - for rm in sorted(removed.keys()): - report += f"**Remove from {rm}**\n\n```\n" - paragraph = ', '.join(removed[rm]) - report += "\n".join(textwrap.wrap(paragraph, width=90, break_long_words=False, break_on_hyphens=False)) - report += "\n```\n\n" - - moved = dict() - for pkg in old_file: - old_groups = old_file[pkg] - new_groups = new_file.get(pkg) - if not new_groups: - continue - movekey = ','.join(old_groups) + ' to ' + ','.join(new_groups) - moved.setdefault(movekey, []) - moved[movekey].append(pkg) - - for move in sorted(moved.keys()): - report += f"**Move from {move}**\n\n```\n" - paragraph = ', '.join(moved[move]) - report += "\n".join(textwrap.wrap(paragraph, width=90, break_long_words=False, break_on_hyphens=False)) - report += "\n```\n\n" - - added = dict() - for pkg in new_file: - if pkg in old_file: - continue - addkey = ','.join(new_file[pkg]) - added.setdefault(addkey, []) - added[addkey].append(pkg) - - for group in sorted(added): - report += f"**Add to {group}**\n\n```\n" - paragraph = ', '.join(added[group]) - report += "\n".join(textwrap.wrap(paragraph, width=90, break_long_words=False, break_on_hyphens=False)) - report += "\n```\n\n" - - return report.strip() - - def handle_package_diff(self, project, old_file, new_file): - comments = self.comment.get_comments(project_name=project) - comment, _ = self.comment.comment_find(comments, MARKER) - - report = self.calculcate_package_diff(old_file, new_file) - if not report: - if comment: - self.comment.delete(comment['id']) - return 0 - report = self.comment.add_marker(report, MARKER) - - if comment: - write_comment = report != comment['comment'] - else: - write_comment = True - if write_comment: - if comment: - self.comment.delete(comment['id']) - self.comment.add_comment(project_name=project, comment=report) - else: - for c in comments.values(): - if c['parent'] == comment['id']: - ct = c['comment'] - if ct.startswith('ignore ') or ct == 'ignore': - print(c) - return 0 - - return 1 - def solve_project(self, ignore_unresolvable=False, ignore_recommended=False, locale=None, locales_from=None): self.load_all_groups() if not self.output: @@ -766,8 +664,6 @@ checkout_package(api.apiurl, project, package, expand_link=True, prj_dir=cache_dir, outdir=os.path.join(cache_dir, package)) - # print('RET', self.handle_package_diff(project, f"{group_dir}/summary-staging.txt", f"{product_dir}/summary-staging.txt")) - file_utils.unlink_all_except(release_dir, ['weakremovers.inc']) if not only_release_packages: file_utils.unlink_all_except(product_dir) @@ -860,4 +756,4 @@ self.commit_package(product_dir) if os.path.isfile(reference_summary): - return self.handle_package_diff(project, reference_summary, summary_file) + return self.comment.handle_package_diff(project, reference_summary, summary_file) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20220321.48333852/staging-installcheck.py new/openSUSE-release-tools-20220323.bd336586/staging-installcheck.py --- old/openSUSE-release-tools-20220321.48333852/staging-installcheck.py 2022-03-21 17:38:05.000000000 +0100 +++ new/openSUSE-release-tools-20220323.bd336586/staging-installcheck.py 2022-03-23 16:41:56.000000000 +0100 @@ -11,7 +11,6 @@ import osc.core import yaml from lxml import etree as ET -from osc import conf from osclib.comments import CommentAPI from osclib.conf import Config @@ -19,6 +18,7 @@ from osclib.core import (builddepinfo, depends_on, duplicated_binaries_in_repo, fileinfo_ext_all, repository_arch_state, repository_path_expand, target_archs) + from osclib.repochecks import installcheck, mirror from osclib.stagingapi import StagingAPI @@ -29,22 +29,21 @@ class InstallChecker(object): def __init__(self, api, config): self.api = api - self.config = conf.config[api.project] self.logger = logging.getLogger('InstallChecker') self.commentapi = CommentAPI(api.apiurl) - self.arch_whitelist = self.config.get('repo_checker-arch-whitelist') + self.arch_whitelist = config.get('repo_checker-arch-whitelist') if self.arch_whitelist: self.arch_whitelist = set(self.arch_whitelist.split(' ')) - self.ring_whitelist = set(self.config.get('repo_checker-binary-whitelist-ring', '').split(' ')) + self.ring_whitelist = set(config.get('repo_checker-binary-whitelist-ring', '').split(' ')) - self.cycle_packages = self.config.get('repo_checker-allowed-in-cycles') + self.cycle_packages = config.get('repo_checker-allowed-in-cycles') self.calculate_allowed_cycles() - self.ignore_duplicated = set(self.config.get('installcheck-ignore-duplicated-binaries', '').split(' ')) - self.ignore_conflicts = set(self.config.get('installcheck-ignore-conflicts', '').split(' ')) - self.ignore_deletes = str2bool(self.config.get('installcheck-ignore-deletes', 'False')) + self.ignore_duplicated = set(config.get('installcheck-ignore-duplicated-binaries', '').split(' ')) + self.ignore_conflicts = set(config.get('installcheck-ignore-conflicts', '').split(' ')) + self.ignore_deletes = str2bool(config.get('installcheck-ignore-deletes', 'False')) def check_required_by(self, fileinfo, provides, requiredby, built_binaries, comments): if requiredby.get('name') in built_binaries: @@ -374,7 +373,7 @@ osc.conf.config['debug'] = args.debug apiurl = osc.conf.config['apiurl'] - config = Config(apiurl, args.project) + config = Config.get(apiurl, args.project) api = StagingAPI(apiurl, args.project) staging_report = InstallChecker(api, config) @@ -383,13 +382,11 @@ else: logging.basicConfig(level=logging.INFO) - result = True if args.staging: - result = staging_report.staging(api.prj_from_short(args.staging), force=True) + if not staging_report.staging(api.prj_from_short(args.staging), force=True): + sys.exit(1) else: for staging in api.get_staging_projects(): if api.is_adi_project(staging): - result = staging_report.staging(staging) and result - - if not result: - logging.error("Found problem") + staging_report.staging(staging) + sys.exit(0) ++++++ openSUSE-release-tools.obsinfo ++++++ --- /var/tmp/diff_new_pack.PVnDVa/_old 2022-03-23 20:21:11.118558041 +0100 +++ /var/tmp/diff_new_pack.PVnDVa/_new 2022-03-23 20:21:11.126558046 +0100 @@ -1,5 +1,5 @@ name: openSUSE-release-tools -version: 20220321.48333852 -mtime: 1647880685 -commit: 4833385205225ad2ef4d4686ac7f2fc2d54a78cc +version: 20220323.bd336586 +mtime: 1648050116 +commit: bd3365863f090bfe86f03dd73b483431c9ed33f3