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 2023-01-02 15:02:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-osc-tiny (Old)
 and      /work/SRC/openSUSE:Factory/.python-osc-tiny.new.1563 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-osc-tiny"

Mon Jan  2 15:02:27 2023 rev:26 rq:1046130 version:0.7.10

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-osc-tiny/python-osc-tiny.changes  
2022-12-13 18:55:41.899277236 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-osc-tiny.new.1563/python-osc-tiny.changes    
    2023-01-02 15:02:34.193486341 +0100
@@ -1,0 +2,9 @@
+Mon Jan  2 09:33:59 UTC 2023 - Andreas Hasenkopf <ahasenk...@suse.com>
+
+- Release 0.7.10
+  * Include the original error message, when an SSH key cannot be read
+  * Added a link to the documentation of `HttpSignatureAuth`
+  * Allow `Package.exists` to raise exceptions
+  * Added methods to get/set the project config
+
+-------------------------------------------------------------------

Old:
----
  osc-tiny-0.7.9.tar.gz

New:
----
  osc-tiny-0.7.10.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-osc-tiny.spec ++++++
--- /var/tmp/diff_new_pack.IFYKUS/_old  2023-01-02 15:02:34.857490072 +0100
+++ /var/tmp/diff_new_pack.IFYKUS/_new  2023-01-02 15:02:34.861490095 +0100
@@ -18,7 +18,7 @@
 
 %define skip_python2 1
 Name:           python-osc-tiny
-Version:        0.7.9
+Version:        0.7.10
 Release:        0
 Summary:        Client API for openSUSE BuildService
 License:        MIT

++++++ osc-tiny-0.7.9.tar.gz -> osc-tiny-0.7.10.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/PKG-INFO new/osc-tiny-0.7.10/PKG-INFO
--- old/osc-tiny-0.7.9/PKG-INFO 2022-12-12 13:21:46.650442600 +0100
+++ new/osc-tiny-0.7.10/PKG-INFO        2023-01-02 10:27:08.909208300 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: osc-tiny
-Version: 0.7.9
+Version: 0.7.10
 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.9/osc_tiny.egg-info/PKG-INFO 
new/osc-tiny-0.7.10/osc_tiny.egg-info/PKG-INFO
--- old/osc-tiny-0.7.9/osc_tiny.egg-info/PKG-INFO       2022-12-12 
13:21:46.000000000 +0100
+++ new/osc-tiny-0.7.10/osc_tiny.egg-info/PKG-INFO      2023-01-02 
10:27:08.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: osc-tiny
-Version: 0.7.9
+Version: 0.7.10
 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.9/osctiny/__init__.py 
new/osc-tiny-0.7.10/osctiny/__init__.py
--- old/osc-tiny-0.7.9/osctiny/__init__.py      2022-12-12 13:21:36.000000000 
+0100
+++ new/osc-tiny-0.7.10/osctiny/__init__.py     2023-01-02 10:26:59.000000000 
+0100
@@ -6,4 +6,4 @@
 
 __all__ = ['Osc', 'bs_requests', 'buildresults', 'comments', 'packages',
            'projects', 'search', 'users']
-__version__ = "0.7.9"
+__version__ = "0.7.10"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/osctiny/extensions/packages.py 
new/osc-tiny-0.7.10/osctiny/extensions/packages.py
--- old/osc-tiny-0.7.9/osctiny/extensions/packages.py   2022-12-12 
13:21:36.000000000 +0100
+++ new/osc-tiny-0.7.10/osctiny/extensions/packages.py  2023-01-02 
10:26:59.000000000 +0100
@@ -470,7 +470,7 @@
 
         return parsed
 
-    def exists(self, project, package, filename=None):
+    def exists(self, project, package, filename=None) -> bool:
         """
         Check whether package or file in package exists
 
@@ -493,6 +493,12 @@
             raise_for_status=False
         )
 
+        if response.status_code == 404:
+            return False
+
+        # 404 is the only acceptable HTTP error code, otherwise raise an 
exception
+        response.raise_for_status()
+
         return response.status_code == 200
 
     # pylint: disable=too-many-locals
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/osctiny/extensions/projects.py 
new/osc-tiny-0.7.10/osctiny/extensions/projects.py
--- old/osc-tiny-0.7.9/osctiny/extensions/projects.py   2022-12-12 
13:21:36.000000000 +0100
+++ new/osc-tiny-0.7.10/osctiny/extensions/projects.py  2023-01-02 
10:26:59.000000000 +0100
@@ -358,6 +358,55 @@
 
         return self.osc.get_objectified_xml(response)
 
+    def get_config(self, project, revision=None):
+        """
+        Get project configuration
+
+        .. versionadded:: 0.7.10
+
+        :param project: name of project
+        :param revision: optional revision of the config to get
+        :return: The project configuration as string
+        :rtype: str
+        """
+        response = self.osc.request(
+            url=urljoin(
+                self.osc.url,
+                "{}/{}/_config".format(self.base_path, project)
+            ),
+            params={"rev":revision},
+            method="GET"
+        )
+
+        return response.text
+
+    def set_config(self, project, config=None, comment=None):
+        """
+        Set project config data
+
+        .. versionadded:: 0.7.10
+
+        :param project: name of project
+        :param config: Complete configuration to set
+        :type config: str
+        :param comment: Optional comment to use as commit message
+        :return: ``True``, if successful. Otherwise, API response
+        :rtype: bool or lxml.objectify.ObjectifiedElement
+        """
+        response = self.osc.request(
+            url=urljoin(self.osc.url,
+                        "/".join((self.base_path, project, "_config"))),
+            method="PUT",
+            data=config,
+            params={"comment": comment}
+        )
+
+        parsed = self.osc.get_objectified_xml(response)
+        if response.status_code == 200 and parsed.get("code") == "ok":
+            return True
+
+        return parsed
+
     def delete(self, project, force=False, comment=None):
         """
         Delete project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/osctiny/tests/test_packages.py 
new/osc-tiny-0.7.10/osctiny/tests/test_packages.py
--- old/osc-tiny-0.7.9/osctiny/tests/test_packages.py   2022-12-12 
13:21:36.000000000 +0100
+++ new/osc-tiny-0.7.10/osctiny/tests/test_packages.py  2023-01-02 
10:26:59.000000000 +0100
@@ -3,6 +3,7 @@
 import re
 from unittest import skip
 
+from requests import HTTPError
 import responses
 
 from .base import OscTest, CallbackFactory
@@ -520,3 +521,30 @@
             self.assertEqual(expected,
                              
self.osc.packages.cleanup_params(view="verboseproductlist",
                                                               expand=True))
+
+    @responses.activate
+    def test_exists(self):
+        data = (
+            # status, expected
+            (200, True),
+            (404, False),
+            (401, None),
+            (500, None)
+        )
+
+        def callback(headers, params, request):
+            return status, headers, ""
+
+        self.mock_request(
+            method=responses.HEAD,
+            url=re.compile(self.osc.url + '/source/.+'),
+            callback=CallbackFactory(callback)
+        )
+
+        for status, expected in data:
+            with self.subTest(status):
+                if expected is not None:
+                    self.assertEqual(expected, 
self.osc.packages.exists("Some:Project", "package"))
+                else:
+                    self.assertRaises(HTTPError, self.osc.packages.exists, 
"Some:Project",
+                                      "package")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/osctiny/tests/test_projects.py 
new/osc-tiny-0.7.10/osctiny/tests/test_projects.py
--- old/osc-tiny-0.7.9/osctiny/tests/test_projects.py   2022-12-12 
13:21:36.000000000 +0100
+++ new/osc-tiny-0.7.10/osctiny/tests/test_projects.py  2023-01-02 
10:26:59.000000000 +0100
@@ -370,6 +370,80 @@
             )
 
     @responses.activate
+    def test_get_config(self):
+        def callback(headers, params, request):
+            status = 200
+            body = "the_config"
+            self.assertTrue("/_config" in request.url)
+            if "Devel:ARM:Factory" not in request.url:
+                status = 404
+                body = """
+                        <status code="unknown_project">
+                          <summary>Devel:ARM:Fbctory</summary>
+                        </status>
+                    """
+            headers['request-id'] = '728d329e-0e86-11e4-a748-0c84dc037c13'
+            return status, headers, body
+
+        self.mock_request(
+            method=responses.GET,
+            url=re.compile(self.osc.url + '/source/.*'),
+            callback=CallbackFactory(callback)
+        )
+
+        with self.subTest("existing project"):
+            response = self.osc.projects.get_config("Devel:ARM:Factory")
+            self.assertEqual(response, "the_config")
+
+        with self.subTest("non-existing project"):
+            self.assertRaises(
+                HTTPError, self.osc.projects.get_config, "Devel:ARM:Fbctory"
+            )
+
+        with self.subTest("existing project with revision"):
+            response = self.osc.projects.get_config("Devel:ARM:Factory", 
revision=3)
+            self.assertEqual(response, "the_config")
+            self.assertEqual(responses.calls[-1].request.params["rev"], "3")
+
+    @responses.activate
+    def test_set_config(self):
+        def callback(headers, params, request):
+            status = 500
+            body = "<status code='error'></status>"
+            match = re.search("source/(?P<project>[^/]+)/_config", request.url)
+            if match and not "invalid" in request.url:
+                status = 200
+                body = """<status code='ok'></status>"""
+
+            headers['request-id'] = '728d329e-0e86-11e4-a748-0c84dc037c13'
+            return status, headers, body
+
+        self.mock_request(
+            method=responses.PUT,
+            url=re.compile(self.osc.url + '/source/[^/]+/_config'),
+            callback=CallbackFactory(callback)
+        )
+
+        with self.subTest("Valid request"):
+            self.assertTrue(self.osc.projects.set_config(
+                project="test",
+                config="foo bar",
+            ))
+
+        with self.subTest("Invalid request"):
+            self.assertRaises(
+                HTTPError,
+                self.osc.projects.set_config,
+                project="invalid:project",
+                config="test_config"
+            )
+
+        with self.subTest("Valid request with comment"):
+            
self.assertTrue(self.osc.projects.set_config(project="project:foo", 
config="test conf",
+                                                         comment="Test"))
+            self.assertEqual(responses.calls[-1].request.params["comment"], 
"Test")
+
+    @responses.activate
     def test_delete(self):
         def callback(headers, params, request):
             if "existing" in request.url:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/osctiny/tests/test_utils.py 
new/osc-tiny-0.7.10/osctiny/tests/test_utils.py
--- old/osc-tiny-0.7.9/osctiny/tests/test_utils.py      2022-12-12 
13:21:36.000000000 +0100
+++ new/osc-tiny-0.7.10/osctiny/tests/test_utils.py     2023-01-02 
10:26:59.000000000 +0100
@@ -396,7 +396,7 @@
 
 @mock.patch("osctiny.utils.auth.time", return_value=123456)
 class TestAuth(TestCase):
-    @mock.patch("osctiny.utils.auth.is_ssh_key_readable", return_value=True)
+    @mock.patch("osctiny.utils.auth.is_ssh_key_readable", return_value=(True, 
None))
     def setUp(self, *_):
         super().setUp()
         mocked_path = mock.MagicMock(spec=Path)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/osctiny/utils/auth.py 
new/osc-tiny-0.7.10/osctiny/utils/auth.py
--- old/osc-tiny-0.7.9/osctiny/utils/auth.py    2022-12-12 13:21:36.000000000 
+0100
+++ new/osc-tiny-0.7.10/osctiny/utils/auth.py   2023-01-02 10:26:59.000000000 
+0100
@@ -54,7 +54,8 @@
     return None
 
 
-def is_ssh_key_readable(ssh_key_file: Path, password: typing.Optional[str]) -> 
bool:
+def is_ssh_key_readable(ssh_key_file: Path, password: typing.Optional[str]) \
+        -> typing.Tuple[bool, typing.Optional[str]]:
     """
     Check whether SSH key can be read/unlocked
 
@@ -67,17 +68,21 @@
     .. versionchanged:: 0.7.8
 
         * Moved from ``HttpSignatureAuth.is_ssh_agent_available``
+
+    .. versionchanged:: 0.7.10
+
+        * Return the error message, if key cannot be unlocked
     """
     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()
+    with Popen(cmd, stdin=DEVNULL, stderr=PIPE, stdout=DEVNULL) as proc:
+        _, error = proc.communicate()
         if proc.returncode == 0:
-            return True
+            return True, None
 
-    return False
+    return False, error.decode("utf-8") if isinstance(error, bytes) else error
 
 
 class HttpSignatureAuth(HTTPDigestAuth):
@@ -88,13 +93,16 @@
 
         This seems to be a variation of the `HTTP Message Signatures`_ 
specification.
 
-        See also the `reference implementation for osc`_
+        See also the `blog post`_ describing the implementation and the
+        `reference implementation for osc`_.
 
         .. _HTTP Message Signatures:
             
https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/
 
         .. _reference implementation for osc: 
https://github.com/openSUSE/osc/pull/1032
 
+        .. _blog post: 
https://www.suse.com/c/multi-factor-authentication-on-suses-build-service/
+
     .. note::
 
         1. It is recommended to use SSH keys with a passphrase.
@@ -110,8 +118,9 @@
         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 is_ssh_key_readable(ssh_key_file=ssh_key_file, 
password=password):
-            raise RuntimeError("SSH signing impossible: Unable to decrypt 
key.")
+        readable, error = is_ssh_key_readable(ssh_key_file=ssh_key_file, 
password=password)
+        if not readable:
+            raise RuntimeError(f"SSH signing impossible because key cannot be 
decrypted: {error}.")
 
         self.ssh_key_file = ssh_key_file
         self.pattern = re.compile(r"(?<=\)) (?=\()")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.7.9/setup.py new/osc-tiny-0.7.10/setup.py
--- old/osc-tiny-0.7.9/setup.py 2022-12-12 13:21:36.000000000 +0100
+++ new/osc-tiny-0.7.10/setup.py        2023-01-02 10:26:59.000000000 +0100
@@ -26,7 +26,7 @@
 
 setup(
     name='osc-tiny',
-    version='0.7.9',
+    version='0.7.10',
     description='Client API for openSUSE BuildService',
     long_description=long_description,
     long_description_content_type="text/markdown",

Reply via email to