Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-osc-tiny for openSUSE:Factory checked in at 2022-12-13 18:55:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-osc-tiny (Old) and /work/SRC/openSUSE:Factory/.python-osc-tiny.new.1835 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-osc-tiny" Tue Dec 13 18:55:24 2022 rev:25 rq:1042475 version:0.7.9 Changes: -------- --- /work/SRC/openSUSE:Factory/python-osc-tiny/python-osc-tiny.changes 2022-11-03 19:14:28.724111325 +0100 +++ /work/SRC/openSUSE:Factory/.python-osc-tiny.new.1835/python-osc-tiny.changes 2022-12-13 18:55:41.899277236 +0100 @@ -1,0 +2,8 @@ +Mon Dec 12 09:09:04 UTC 2022 - Andreas Hasenkopf <ahasenk...@suse.com> + +- Release 0.7.9 + * Simplified handling of SSH keys (fixes #114) + * Replaced `Request.cmd` with `Request.update` (fixes #113) + * Added a comment parameter to project and package `set_meta` + +------------------------------------------------------------------- Old: ---- osc-tiny-0.7.7.tar.gz New: ---- osc-tiny-0.7.9.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-osc-tiny.spec ++++++ --- /var/tmp/diff_new_pack.BzirB0/_old 2022-12-13 18:55:44.019288551 +0100 +++ /var/tmp/diff_new_pack.BzirB0/_new 2022-12-13 18:55:44.027288594 +0100 @@ -18,7 +18,7 @@ %define skip_python2 1 Name: python-osc-tiny -Version: 0.7.7 +Version: 0.7.9 Release: 0 Summary: Client API for openSUSE BuildService License: MIT ++++++ osc-tiny-0.7.7.tar.gz -> osc-tiny-0.7.9.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/PKG-INFO new/osc-tiny-0.7.9/PKG-INFO --- old/osc-tiny-0.7.7/PKG-INFO 2022-11-02 13:06:41.584857200 +0100 +++ new/osc-tiny-0.7.9/PKG-INFO 2022-12-12 13:21:46.650442600 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: osc-tiny -Version: 0.7.7 +Version: 0.7.9 Summary: Client API for openSUSE BuildService Home-page: http://github.com/crazyscientist/osc-tiny Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osc_tiny.egg-info/PKG-INFO new/osc-tiny-0.7.9/osc_tiny.egg-info/PKG-INFO --- old/osc-tiny-0.7.7/osc_tiny.egg-info/PKG-INFO 2022-11-02 13:06:41.000000000 +0100 +++ new/osc-tiny-0.7.9/osc_tiny.egg-info/PKG-INFO 2022-12-12 13:21:46.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: osc-tiny -Version: 0.7.7 +Version: 0.7.9 Summary: Client API for openSUSE BuildService Home-page: http://github.com/crazyscientist/osc-tiny Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/__init__.py new/osc-tiny-0.7.9/osctiny/__init__.py --- old/osc-tiny-0.7.7/osctiny/__init__.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/__init__.py 2022-12-12 13:21:36.000000000 +0100 @@ -6,4 +6,4 @@ __all__ = ['Osc', 'bs_requests', 'buildresults', 'comments', 'packages', 'projects', 'search', 'users'] -__version__ = "0.7.7" +__version__ = "0.7.9" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/extensions/bs_requests.py new/osc-tiny-0.7.9/osctiny/extensions/bs_requests.py --- old/osc-tiny-0.7.7/osctiny/extensions/bs_requests.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/extensions/bs_requests.py 2022-12-12 13:21:36.000000000 +0100 @@ -4,6 +4,8 @@ """ from urllib.parse import urljoin +from lxml.etree import XMLSyntaxError + from ..utils.base import ExtensionBase @@ -63,6 +65,37 @@ return self.osc.get_objectified_xml(response) + def update(self, request_id, **kwargs): + """ + Update request or execute command + + .. note:: + + In most cases, the API returns an XML response, so this method will try to return an + objectified XML element. Otherwise, if parsing fails due to a syntax error, the response + body is returned as plain text. In case of all other errors, this method lets the + caller handle the exception. + + :param request_id: ID of the request + :param kwargs: See Build Service + `API documentation <https://build.opensuse.org/apidocs/index>`_ for accepted + keys and values. + :return: Response content + :rtype: lxml.objectify.ObjectifiedElement or plain text + + .. versionadded:: 0.7.8 + """ + request_id = self._validate_id(request_id) + response = self.osc.request( + url=urljoin(self.osc.url, self.base_path + request_id), + method="POST", + params=kwargs + ) + try: + return self.osc.get_objectified_xml(response) + except XMLSyntaxError: + return response.text + def cmd(self, request_id, cmd="diff", **kwargs): """ Get the result of the specified command @@ -88,34 +121,14 @@ * Added ``addreview`` to list of allowed commands * Added validation for arguments of command ``changereviewstate`` - """ - allowed_cmds = ['diff', 'changereviewstate', 'addreview'] - allowed_review_states = ['new', 'accepted', 'declined', 'deleted', - 'revoked', 'superseded'] - if cmd not in allowed_cmds: - raise ValueError("Invalid command: '{}'. Use one of: {}".format( - cmd, ", ".join(allowed_cmds) - )) - if cmd == "changereviewstate"\ - and kwargs.get("newstate", None) not in allowed_review_states: - raise ValueError( - "Invalid review state: '{}'. Use one of: {}".format( - kwargs.get("newstate", None), allowed_review_states - ) - ) + .. deprecated:: 0.7.8 + * Replaced by :py:meth:`update` + """ kwargs["cmd"] = cmd request_id = self._validate_id(request_id) - response = self.osc.request( - url=urljoin(self.osc.url, self.base_path + request_id), - method="POST", - params=kwargs - ) - - if kwargs.get("view", "plain") == "xml": - return self.osc.get_objectified_xml(response) - return response.text + return self.update(request_id=request_id, **kwargs) def add_comment(self, request_id, comment, parent_id=None): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/extensions/packages.py new/osc-tiny-0.7.9/osctiny/extensions/packages.py --- old/osc-tiny-0.7.7/osctiny/extensions/packages.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/extensions/packages.py 2022-12-12 13:21:36.000000000 +0100 @@ -115,7 +115,7 @@ # pylint: disable=too-many-arguments,protected-access def set_meta(self, project, package, title=None, description=None, - meta=None): + meta=None, comment=None): """ Set package metadata @@ -130,12 +130,17 @@ .. versionadded:: 0.1.2 + .. versionchanged:: 0.7.8 + + Added an optional ``comment`` argument to be used as commit message + :param project: Project name :param package: New package name :param title: Title for meta :param description: Description for meta :param meta: New content for meta :type meta: str or lxml.objectify.ObjectifiedElement + :param comment: Optional comment to use as commit message :return: """ if isinstance(meta, (str, bytes)): @@ -157,6 +162,7 @@ "/".join((self.base_path, project, package, "_meta")) ), data=tounicode(meta_xml), + params={"comment": comment}, method="PUT" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/extensions/projects.py new/osc-tiny-0.7.9/osctiny/extensions/projects.py --- old/osc-tiny-0.7.7/osctiny/extensions/projects.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/extensions/projects.py 2022-12-12 13:21:36.000000000 +0100 @@ -72,6 +72,7 @@ .. deprecated:: 0.7.2 Use :meth:`set_meta` instead + :param project: name of project :param metafile: Complete metafile :type metafile: str or ElementTree @@ -85,11 +86,11 @@ warn("Deprecated. Use projects.set_meta instead") return self.set_meta(project, metafile, title, description, bugowner, - maintainer) + maintainer) # pylint: disable=too-many-arguments def set_meta(self, project, metafile=None, title=None, description=None, - bugowner=None, maintainer=None): + bugowner=None, maintainer=None, comment=None): """ Edit project meta data or create a new project @@ -97,6 +98,10 @@ .. versionadded:: 0.7.2 + .. versionchanged:: 0.7.8 + + Added an optional ``comment`` argument to be used as commit message + :param project: name of project :param metafile: Complete metafile :type metafile: str or ElementTree @@ -104,6 +109,7 @@ :param description: Description for meta file :param bugowner: Bugowner for meta file :param maintainer: Maintainer for meta file + :param comment: Optional comment to use as commit message :return: ``True``, if successful. Otherwise API response :rtype: bool or lxml.objectify.ObjectifiedElement """ @@ -143,7 +149,8 @@ url=urljoin(self.osc.url, "/".join((self.base_path, project, "_meta"))), method="PUT", - data=tounicode(metafile) + data=tounicode(metafile), + params={"comment": comment} ) parsed = self.osc.get_objectified_xml(response) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/tests/test_packages.py new/osc-tiny-0.7.9/osctiny/tests/test_packages.py --- old/osc-tiny-0.7.7/osctiny/tests/test_packages.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/tests/test_packages.py 2022-12-12 13:21:36.000000000 +0100 @@ -336,6 +336,23 @@ self.assertEqual(bodies[-1], expected) @responses.activate + def test_set_meta_with_comment(self): + def callback(headers, params, request): + status, body = 200, "" + + return status, headers, body + + self.mock_request( + method=responses.PUT, + url=re.compile(self.osc.url + '/source/(?P<project>[^/]+)/' + '(?P<package>[^/]+)/_meta'), + callback=CallbackFactory(callback) + ) + self.osc.packages.set_meta("test:project", "test.package", "test title", "test description", + comment="test comment") + self.assertEqual(responses.calls[-1].request.params["comment"], "test comment") + + @responses.activate def test_push_file(self): content = """ á(ಠçಠ)á á(ಠçಠ)á diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/tests/test_projects.py new/osc-tiny-0.7.9/osctiny/tests/test_projects.py --- old/osc-tiny-0.7.7/osctiny/tests/test_projects.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/tests/test_projects.py 2022-12-12 13:21:36.000000000 +0100 @@ -177,6 +177,14 @@ self.assertEqual(sent_meta.description.text, "test description") self.assertEqual(len(sent_meta.xpath("person")), 2) + with self.subTest("Valid Metafile with comment"): + meta = fromstring(TEMPLATE_META) + meta.set("name", "project:foo") + meta.title._setText("Hello World") + self.assertTrue(self.osc.projects.set_meta(project="project:foo", metafile=meta, + comment="Test")) + self.assertEqual(responses.calls[-1].request.params["comment"], "Test") + @responses.activate def test_get_files(self): def callback(headers, params, request): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/tests/test_requests.py new/osc-tiny-0.7.9/osctiny/tests/test_requests.py --- old/osc-tiny-0.7.7/osctiny/tests/test_requests.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/tests/test_requests.py 2022-12-12 13:21:36.000000000 +0100 @@ -372,20 +372,16 @@ self.assertEqual(len(response.xpath("//request/history")), 5) @responses.activate - def test_cmd(self): - with self.subTest("plain diff"): - response = self.osc.requests.cmd(30902, "diff") - self.assertTrue(isinstance(response, str)) - self.assertIn("changes files:", response) - self.assertIn("+++ perl-XML-DOM-XPath.changes", response) - with self.subTest("xml diff"): - response = self.osc.requests.cmd(30902, "diff", view="xml") - self.assertTrue(isinstance(response, ObjectifiedElement)) - with self.subTest("changereviewstate"): - self.assertRaises( - ValueError, - self.osc.requests.cmd, 30902, "changereviewstate" - ) + def test_update(self): + for method in (self.osc.requests.update, self.osc.requests.cmd): + with self.subTest(f"{method}, cmd=diff, plain"): + response = method(30902, cmd="diff") + self.assertTrue(isinstance(response, str)) + self.assertIn("changes files:", response) + self.assertIn("+++ perl-XML-DOM-XPath.changes", response) + with self.subTest(f"{method}, cmd=diff, xml"): + response = method(30902, cmd="diff", view="xml") + self.assertTrue(isinstance(response, ObjectifiedElement)) @responses.activate def test_comment(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/tests/test_utils.py new/osc-tiny-0.7.9/osctiny/tests/test_utils.py --- old/osc-tiny-0.7.7/osctiny/tests/test_utils.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/tests/test_utils.py 2022-12-12 13:21:36.000000000 +0100 @@ -396,7 +396,8 @@ @mock.patch("osctiny.utils.auth.time", return_value=123456) class TestAuth(TestCase): - def setUp(self): + @mock.patch("osctiny.utils.auth.is_ssh_key_readable", return_value=True) + def setUp(self, *_): super().setUp() mocked_path = mock.MagicMock(spec=Path) mocked_path.configure_mock(**{"is_file.return_value": True}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/utils/auth.py new/osc-tiny-0.7.9/osctiny/utils/auth.py --- old/osc-tiny-0.7.7/osctiny/utils/auth.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/utils/auth.py 2022-12-12 13:21:36.000000000 +0100 @@ -7,9 +7,8 @@ import typing from base64 import b64decode, b64encode import logging -import os from pathlib import Path -from subprocess import Popen, PIPE +from subprocess import Popen, PIPE, DEVNULL import re import sys from time import time @@ -55,6 +54,32 @@ return None +def is_ssh_key_readable(ssh_key_file: Path, password: typing.Optional[str]) -> bool: + """ + Check whether SSH key can be read/unlocked + + :param ssh_key_file: Path to SSH key + :param password: Passphrase + :return: ``True``, if SSH key is accessible + + .. versionadded:: 0.6.3 + + .. versionchanged:: 0.7.8 + + * Moved from ``HttpSignatureAuth.is_ssh_agent_available`` + """ + cmd = ['ssh-keygen', '-y', '-f', ssh_key_file.as_posix()] + if password: + cmd += ['-P', password] + + with Popen(cmd, stdin=DEVNULL, stderr=DEVNULL, stdout=DEVNULL) as proc: + proc.communicate() + if proc.returncode == 0: + return True + + return False + + class HttpSignatureAuth(HTTPDigestAuth): """ Implementation of the "Signature authentication scheme" @@ -70,17 +95,24 @@ .. _reference implementation for osc: https://github.com/openSUSE/osc/pull/1032 + .. note:: + + 1. It is recommended to use SSH keys with a passphrase. + 2. If ``ssh-agent`` is running, the passphrase is not required at initialization of this + class. + 3. If you use an SSH key without passphrase, you don't need to specify it. + :param username: The username - :param password: Passphrase for SSH key. This can be omitted, if ``ssh-agent`` is also installed - :param ssh_key_file: Path of SSK key + :param password: Passphrase for SSH key + :param ssh_key_file: Path of SSH key """ def __init__(self, username: str, password: typing.Optional[str], ssh_key_file: Path): super().__init__(username=username, password=password) if not ssh_key_file.is_file(): raise FileNotFoundError(f"SSH key at location does not exist: {ssh_key_file}") - if not password and not self.is_ssh_agent_available(): - raise RuntimeError("SSH signing impossible: No password/passphrase provided and no SSH " - "agent running! ") + if not is_ssh_key_readable(ssh_key_file=ssh_key_file, password=password): + raise RuntimeError("SSH signing impossible: Unable to decrypt key.") + self.ssh_key_file = ssh_key_file self.pattern = re.compile(r"(?<=\)) (?=\()") @@ -96,19 +128,6 @@ parts = self.pattern.split(headers) return [part.strip("()") for part in parts] - @staticmethod - def is_ssh_agent_available() -> bool: - """ - Check whether SSH agent is running/available - - :return: ``True``, if agent is running - - .. versionadded:: 0.6.3 - """ - relevant_keys = {'SSH_AUTH_SOCK', 'SSH_AGENT_PID'} - overlap = os.environ.keys() & relevant_keys - return len(overlap) > 0 - def ssh_sign(self) -> str: """ Solve the challenge via SSH signing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/osctiny/utils/changelog.py new/osc-tiny-0.7.9/osctiny/utils/changelog.py --- old/osc-tiny-0.7.7/osctiny/utils/changelog.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/osctiny/utils/changelog.py 2022-12-12 13:21:36.000000000 +0100 @@ -118,6 +118,7 @@ cl.write() .. py:attribute:: entry_factory + :noindex: A class used to store entry data. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.7.7/setup.py new/osc-tiny-0.7.9/setup.py --- old/osc-tiny-0.7.7/setup.py 2022-11-02 13:06:32.000000000 +0100 +++ new/osc-tiny-0.7.9/setup.py 2022-12-12 13:21:36.000000000 +0100 @@ -26,7 +26,7 @@ setup( name='osc-tiny', - version='0.7.7', + version='0.7.9', description='Client API for openSUSE BuildService', long_description=long_description, long_description_content_type="text/markdown",