Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package fuse-oscfs for openSUSE:Factory 
checked in at 2023-10-20 23:18:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/fuse-oscfs (Old)
 and      /work/SRC/openSUSE:Factory/.fuse-oscfs.new.1945 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "fuse-oscfs"

Fri Oct 20 23:18:46 2023 rev:6 rq:1119201 version:0.9.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/fuse-oscfs/fuse-oscfs.changes    2022-02-23 
16:26:59.455509991 +0100
+++ /work/SRC/openSUSE:Factory/.fuse-oscfs.new.1945/fuse-oscfs.changes  
2023-10-20 23:21:55.889825592 +0200
@@ -1,0 +2,24 @@
+Fri Oct 20 09:27:57 UTC 2023 - matthias.gerst...@suse.com
+
+- Update to version 0.9.0:
+  * NEWS: changes for next release
+  * regtest: compatibility with newer OSC module versions
+  * obs: implement transparent retry to prevent HTTP 503 woes
+  * obs: remove extraneous newlines from project and package meta output
+  * error handling: support full exception backtrace report
+  * debugging: support writing stdout and stderr to a logfile for analysis
+  * regtest: fix unmount in early auth error failure case
+  * regtest: make sure also to remove cookiejar for early auth error test
+  * Revert "regtest: add some extra robustness against potential umount races"
+  * regtest: add some extra robustness against potential umount races
+  * fix regressions introduced by commit 
cd692a1d2f9fabe4ac5451832c3b7df109302c07
+  * urlopenwrapper: fix sshkey check
+  * Package: fix symlink path to update package
+  * urlopen wrapper: support disabling the wrapper on the command line
+  * obs.getBinaries(): fix strange osc behaviour if zero length files appear
+  * add command line option to disable build artifacts caching
+  * module_helper: insert local modules directory at the front of the path
+  * oscfs wrapper script: use explicitly python3 by default
+  * bugfix: fix early auth check on non-OBS instance
+
+-------------------------------------------------------------------

Old:
----
  oscfs-v0.8.1.tar.xz

New:
----
  oscfs-0.9.0.tar.xz

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

Other differences:
------------------
++++++ fuse-oscfs.spec ++++++
--- /var/tmp/diff_new_pack.XlfGXf/_old  2023-10-20 23:21:56.433845442 +0200
+++ /var/tmp/diff_new_pack.XlfGXf/_new  2023-10-20 23:21:56.433845442 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package fuse-oscfs
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,13 +17,13 @@
 
 
 Name:           fuse-oscfs
-Version:        0.8.1
+Version:        0.9.0
 Release:        0
 Summary:        A FUSE file system for accessing Open Build Service instances
 License:        GPL-2.0-or-later
 Group:          System/Filesystems
 URL:            https://github.com/mgerstner/oscfs
-Source:         oscfs-v%{version}.tar.xz
+Source:         oscfs-%{version}.tar.xz
 BuildRequires:  python-rpm-macros
 BuildRequires:  python3-setuptools
 Requires:       osc
@@ -40,7 +40,7 @@
 access for inspecting packages and their metadata.
 
 %prep
-%setup -q -n oscfs-v%{version}
+%setup -q -n oscfs-%{version}
 
 %build
 python3 setup.py build

++++++ _service ++++++
--- /var/tmp/diff_new_pack.XlfGXf/_old  2023-10-20 23:21:56.461846463 +0200
+++ /var/tmp/diff_new_pack.XlfGXf/_new  2023-10-20 23:21:56.465846609 +0200
@@ -2,7 +2,7 @@
   <service name="tar_scm" mode="disabled">
     <param name="url">https://github.com/mgerstner/oscfs.git</param>
     <param name="scm">git</param>
-    <param name="revision">v0.8.1</param>
+    <param name="revision">0.9.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="changesgenerate">enable</param>
   </service>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.XlfGXf/_old  2023-10-20 23:21:56.481847192 +0200
+++ /var/tmp/diff_new_pack.XlfGXf/_new  2023-10-20 23:21:56.485847339 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/mgerstner/oscfs.git</param>
-              <param 
name="changesrevision">1153b097df63f57552bce553ddba3944a55e065d</param></service></servicedata>
+              <param 
name="changesrevision">efa4a91b6fbe54a33eb51c04728706555e5106c6</param></service></servicedata>
 (No newline at EOF)
 

++++++ oscfs-v0.8.1.tar.xz -> oscfs-0.9.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/.gitignore new/oscfs-0.9.0/.gitignore
--- old/oscfs-v0.8.1/.gitignore 1970-01-01 01:00:00.000000000 +0100
+++ new/oscfs-0.9.0/.gitignore  2023-10-20 11:03:57.000000000 +0200
@@ -0,0 +1,3 @@
+*.pyc
+__pycache__
+*.swp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/NEWS.rst new/oscfs-0.9.0/NEWS.rst
--- old/oscfs-v0.8.1/NEWS.rst   2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/NEWS.rst    2023-10-20 11:03:57.000000000 +0200
@@ -1,3 +1,22 @@
+0.9.0
+=====
+
+- use Python3 by default now.
+- various small bugfixes:
+  - fix error if a binary build artifact has zero file size
+  - fix symlink target in package update links (symlink to maintenance
+    incidents).
+- new --no-urlopen-wrapper command line option to disable the HTTP connection
+  reuse hack. This is helpful when working against build.suse.de, where
+  two-factor-authentication is now required and the hack doesn't work with
+  that anymore.
+- new --no-bin-cache command line option to disable caching of large binary 
files.
+- new --use-logfile command line option to write errors and debugging
+  information to file. This is helpful when oscfs is running as a daemon and
+  stdout/stderr are discarded.
+- implements transparent retry if the OBS server responds with HTTP status 503
+  (service unavailable).
+
 0.8.1
 =====
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/bin/module_helper.py 
new/oscfs-0.9.0/bin/module_helper.py
--- old/oscfs-v0.8.1/bin/module_helper.py       2022-02-23 11:16:38.000000000 
+0100
+++ new/oscfs-0.9.0/bin/module_helper.py        2023-10-20 11:03:57.000000000 
+0200
@@ -9,7 +9,7 @@
     """Adds .. to the current module path, tries to import $module and exits 
if it is not found."""
     # get the full path of the parent directory and append the name
     parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
-    sys.path.append(parent_dir)
+    sys.path.insert(0, parent_dir)
 
     # check if the module can be loaded now
     if pkgutil.find_loader(module) is None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/bin/oscfs new/oscfs-0.9.0/bin/oscfs
--- old/oscfs-v0.8.1/bin/oscfs  2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/bin/oscfs   2023-10-20 11:03:57.000000000 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 try:
     import module_helper  # noqa: F401
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/fs.py new/oscfs-0.9.0/oscfs/fs.py
--- old/oscfs-v0.8.1/oscfs/fs.py        2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/oscfs/fs.py 2023-10-20 11:03:57.000000000 +0200
@@ -6,9 +6,16 @@
 # third party modules
 import fuse
 
+# explicitly parse this option since we need to import the wrapper before
+# oscfs.obs is imported
+if "--no-urlopen-wrapper" not in sys.argv:
+    # urllib2 replacement, needs to be imported before osc.core
+    import oscfs.urlopenwrapper
+
 # local modules
 import oscfs.obs
 import oscfs.root
+from oscfs.package import BinaryFileNode
 
 
 class OscFs(fuse.LoggingMixIn, fuse.Operations):
@@ -53,6 +60,16 @@
             help="If set then PTF projects will be included which is not the 
default"
         )
         self.m_parser.add_argument(
+            "--no-bin-cache", action='store_true',
+            help="If set then binary files like RPMs and other build artifacts 
will not be cached. This prevents linearly increasing memory usage in case a 
lot of these files are accessed over time."
+        )
+        self.m_parser.add_argument(
+            "--no-urlopen-wrapper", action='store_true',
+            help="Disable use of a hack to replace urllib's urlopen function 
to improve performance."
+        )
+        self.m_parser.add_argument(
+            "--use-logfile", nargs='?', 
const=f'/run/user/{os.getuid()}/oscfs.log.{os.getpid()}')
+        self.m_parser.add_argument(
             "mountpoint", type=str,
             help="Path where to mount the file system"
         )
@@ -91,10 +108,10 @@
                 print("HTTP error occured trying to access the remote server:")
                 print(e)
         except Exception as e:
-            print(
-                "Accessing the remote server failed:",
-                e, file=sys.stderr
-            )
+            if str(e).find("unknown_project") != -1:
+                # 404 on XML level
+                return
+            print("Accessing the remote server failed:", e, file=sys.stderr)
             raise
 
         sys.exit(1)
@@ -109,13 +126,29 @@
             except KeyError:
                 raise fuse.FuseOSError(errno.ENOENT)
 
+    def _setupLogfile(self):
+
+        logfile = self.m_args.use_logfile
+
+        if not logfile:
+            return
+
+        print("Redirecting output to logfile in", logfile)
+
+        lf = open(logfile, 'a')
+        sys.stdout = lf
+        sys.stderr = lf
+
     def run(self):
 
         self.m_args = self.m_parser.parse_args()
+        self._setupLogfile()
         self.m_obs.configure(self.m_args.apiurl)
         self.m_root = oscfs.root.Root(self.m_obs, self.m_args)
         if self.m_args.cache_time is not None:
             oscfs.types.Node.setMaxCacheTime(self.m_args.cache_time)
+        if self.m_args.no_bin_cache:
+            BinaryFileNode.cache_binaries = False
 
         self._checkAuth()
 
@@ -176,10 +209,13 @@
 
         self.m_handles[fd] = node
 
+        node.incUsers()
+
         return fd
 
     def _freeFileHandle(self, fh):
 
+        self.m_handles[fh].decUsers()
         self.m_handles[fh] = None
         self.m_free_handles.append(fh)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/misc.py 
new/oscfs-0.9.0/oscfs/misc.py
--- old/oscfs-v0.8.1/oscfs/misc.py      2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/oscfs/misc.py       2023-10-20 11:03:57.000000000 +0200
@@ -34,6 +34,27 @@
     return "Exception in {}:{}: {}".format(fn, ln, str(ex))
 
 
+def getExceptionTrace(ex):
+    import sys
+    import traceback
+    _, _, tb = sys.exc_info()
+    first = True
+    ret = []
+    for frame in traceback.extract_tb(tb):
+        fn, ln, func, ln_text = frame
+
+        ln = str(ln).rjust(4)
+
+        if first:
+            line = f"{fn}:{ln} {func}(): {str(ex)}"
+            ret.append(line)
+            first = False
+        else:
+            ret.append(f"{fn}:{ln} {func}()")
+
+    return '\n'.join(ret)
+
+
 def printException(ex):
     """Prints the currently active exception in a friendly, compact
     way to stderr."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/obs.py 
new/oscfs-0.9.0/oscfs/obs.py
--- old/oscfs-v0.8.1/oscfs/obs.py       2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/oscfs/obs.py        2023-10-20 11:03:57.000000000 +0200
@@ -2,12 +2,11 @@
 from xml.etree import cElementTree as et
 import datetime
 
-# urllib2 replacement, needs to be imported before osc.core
-import oscfs.urlopenwrapper
-
 # third party modules
 import osc.core
 
+from oscfs.retry_decorator import transparent_retry
+
 
 class Obs:
     """Wrapper around the osc python module for the purposes of this file
@@ -21,49 +20,47 @@
         import osc.conf
         self.m_apiurl = apiurl
         try:
-            osc.conf.get_config()
+            osc.conf.get_config(override_apiurl=apiurl)
         except osc.oscerr.NoConfigfile:
             raise Exception("No .oscrc config file found. Please configure OSC 
first.")
         except osc.oscerr.ConfigError as e:
             raise Exception(f"No valid configuration found in .oscrc: {e}. 
Please configure OSC first.")
 
-        osc.conf.config["apiurl"] = apiurl
-
     def getUser(self):
         return osc.conf.config["user"]
 
+    @transparent_retry()
     def getProjectList(self):
         """Returns a list of the top-level projects of the current OBS
         instance. It's a list of plain strings."""
 
-        ret = osc.core.meta_get_project_list(self.m_apiurl)
-
-        return ret
+        return osc.core.meta_get_project_list(self.m_apiurl)
 
+    @transparent_retry(expect_xml=True)
     def getProjectMeta(self, project):
         """Returns a string consisting of the XML that makes up the
         specified project's metadata."""
 
         xml_lines = osc.core.show_project_meta(self.m_apiurl, project)
-
-        return '\n'.join([line.decode() for line in xml_lines])
+        return ''.join([line.decode() for line in xml_lines])
 
     def getProjectInfo(self, project):
         """Returns an object of type ProjectInfo for the given
         project."""
+
         xml = self.getProjectMeta(project)
         return ProjectInfo(xml)
 
+    @transparent_retry()
     def getPackageList(self, project):
         """Returns a list of all the packages within a top-level
         project. It's a list of plain strings."""
 
-        ret = osc.core.meta_get_packagelist(self.m_apiurl, project)
-
-        return ret
+        return osc.core.meta_get_packagelist(self.m_apiurl, project)
 
+    @transparent_retry(expect_xml=True)
     def _getPackageFileTree(self, project, package, revision=None):
-        xml = osc.core.show_files_meta(
+        return osc.core.show_files_meta(
             self.m_apiurl,
             project,
             package,
@@ -71,19 +68,17 @@
             meta=False
         )
 
-        tree = et.fromstring(xml)
-        return tree
-
     def getPackageFileList(self, project, package, revision=None):
         """Returns a list of the files belonging to a package. The
         list is comprised of tuples of the form (name, size,
         modtime)."""
 
-        tree = self._getPackageFileTree(
+        xml = self._getPackageFileTree(
             project,
             package,
             revision=revision
         )
+        tree = et.fromstring(xml)
         ret = []
 
         # if this is a linked package then we find two nodes
@@ -126,6 +121,7 @@
 
         return ret
 
+    @transparent_retry()
     def getPackageRequestList(self, project, package, states=None):
         """Returns a list of osc.core.Request instances representing
         the existing requests for the given project/package.
@@ -164,6 +160,7 @@
         comps = ['build', project, repo, arch, package, _file]
         return self._download(comps)
 
+    @transparent_retry()
     def _download(self, urlcomps, query=dict()):
 
         import urllib.parse
@@ -182,6 +179,7 @@
 
         return f.read()
 
+    @transparent_retry()
     def _getPackageRevisions(self, project, package, fmt):
         """Returns the list of revisions for the given project/package
         path in the given fmt. @fmt can be any of ('text, 'csv',
@@ -237,6 +235,7 @@
             key=lambda r: r.getRevision()
         )
 
+    @transparent_retry(expect_xml=True)
     def getPackageMeta(self, project, package):
         """Returns a string consisting of the XML that makes up the
         specified package's metadata."""
@@ -247,7 +246,7 @@
             package
         )
 
-        return '\n'.join([line.decode() for line in xml_lines])
+        return ''.join([line.decode() for line in xml_lines])
 
     def getPackageInfo(self, project, package):
         """Returns an object of type PackageInfo for the given
@@ -255,6 +254,7 @@
         xml = self.getPackageMeta(project, package)
         return PackageInfo(xml)
 
+    @transparent_retry()
     def getBuildlog(self, project, package, repo, arch):
         """Returns the plaintext build log for the given build
         configuration. This can be empty if the build is not
@@ -268,16 +268,17 @@
             "_log?start=0&nostream=1"
         ])
 
-        ret = b""
-
         # NOTE: streamfile supports bufsize="line" to read line wise
         # and supports yield semantics. This breaks with our
-        # urlopenwrapper hack, however so we don't use it.
+        # urlopenwrapper hack, however, so we don't use it.
+        ret = b""
+
         for line in osc.core.streamfile(url):
             ret += line
 
         return ret
 
+    @transparent_retry(expect_xml=True)
     def getBuildResultsMeta(self, project, package, repo=[], arch=[]):
         """Returns XML data describing the build status of the given
         project/package combination. If repo and arch are given then
@@ -304,6 +305,7 @@
 
         return BuildResultList(xml)
 
+    @transparent_retry()
     def getBinaryList(self, project, package, repo, arch):
         """Returns a list of tuples representing the binary
         artifacts of the given build configuration. Each tuple
@@ -315,7 +317,8 @@
             verbose=True
         )
 
-        return [(f.name, f.mtime, f.size) for f in ret]
+        # NOTE: this can return None if the size is zero, this is fixing that
+        return [(f.name, f.mtime, 0 if f.size is None else f.size) for f in 
ret]
 
 
 class CommitInfo:
@@ -714,6 +717,9 @@
         self.reset()
         tree = et.fromstring(meta_xml)
 
+        if tree.tag == "status":
+            raise Exception(f"failed to retrieve project info: 
{tree.attrib['code']}")
+
         name = tree.attrib["name"]
         self.setName(name)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/package.py 
new/oscfs-0.9.0/oscfs/package.py
--- old/oscfs-v0.8.1/oscfs/package.py   2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/oscfs/package.py    2023-10-20 11:03:57.000000000 +0200
@@ -209,7 +209,7 @@
         for rev in range(len(infos)):
             info = infos[rev]
 
-            commit = "{rev + 1}"
+            commit = f"{rev + 1}"
             self.m_entries[commit] = CommitNode(self, info, commit)
 
 
@@ -523,6 +523,8 @@
 class BinaryFileNode(oscfs.types.FileNode):
     """This type returns a certain binary artifact's data upon read."""
 
+    cache_binaries = True
+
     def __init__(self, parent, project, package, repo, arch, binary):
 
         super(BinaryFileNode, self).__init__(parent, binary[0])
@@ -551,6 +553,10 @@
         content = obs.getBinaryFileContent(*args)
         self.setContent(content)
 
+    def noUsersLeft(self):
+        if not self.cache_binaries:
+            self.dropCache()
+
 
 class BinariesDir(oscfs.types.DirNode):
     """This type provides access to a repository/arch hierarchy that
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/retry_decorator.py 
new/oscfs-0.9.0/oscfs/retry_decorator.py
--- old/oscfs-v0.8.1/oscfs/retry_decorator.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/oscfs-0.9.0/oscfs/retry_decorator.py    2023-10-20 11:03:57.000000000 
+0200
@@ -0,0 +1,58 @@
+import urllib
+
+
+def _logRetry(ex):
+    from oscfs.misc import getExceptionTrace
+    from sys import stderr
+    trace = getExceptionTrace(ex)
+    print(f"HTTP status 503 service unavailable transparent retry 
occured:\n{trace}\n", file=stderr)
+
+
+# OBS randomly fails with HTTP 503 "service unavailable".
+#
+# This can happen both via HTTPError exceptions or via a document of
+# this form returned instead of XML:
+#
+# <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n<html><head>\n<title>503 
Service Unavailable</title>\n</head><body>\n<h1>Service 
Unavailable</h1>\n<p>The server is temporarily unable to service your\nrequest 
due to maintenance downtime or capacity\nproblems. Please try again 
later.</p>\n<p>Additionally, a 503 Service Unavailable\nerror was encountered 
while trying to use an ErrorDocument to handle the request.</p>\n</body></html>
+#
+# There currently seems no easy way to solve this on the server end, so
+# we are required to transparently retry in this case. Ugly.
+#
+# This is a function decorator that easily allows to add transparent retry
+# behaviour to affected OBS API calls.
+def transparent_retry(expect_xml=False):
+
+    def inner_decorator(func):
+
+        def retry_loop(*args, **kwargs):
+            while True:
+                try:
+                    ret = func(*args, **kwargs)
+                except urllib.error.HTTPError as e:
+                    if e.code == 503:
+                        _logRetry(e)
+                        continue
+                    # on any other error simply re-raise the exception to the
+                    # original caller
+                    raise
+
+                if expect_xml:
+                    try:
+                        text = ret.decode()
+                    except Exception:
+                        text = ret
+
+                    # heuristic to detect this, the osc module seems to fail to
+                    # detect the error status, or there is none sent by the 
server.
+                    if text.lower().find("503 service unavailable") != -1:
+                        try:
+                            raise Exception("503 transparent retry (xml)")
+                        except Exception as e:
+                            _logRetry(e)
+                        continue
+
+                return ret
+
+        return retry_loop
+
+    return inner_decorator
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/types.py 
new/oscfs-0.9.0/oscfs/types.py
--- old/oscfs-v0.8.1/oscfs/types.py     2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/oscfs/types.py      2023-10-20 11:03:57.000000000 +0200
@@ -115,11 +115,26 @@
         self.setType(_type)
         self.m_last_updated = None
         self.m_auto_clear_on_update = True
+        self.m_num_users = 0
 
     @classmethod
     def setMaxCacheTime(cls, seconds):
         cls.max_cache_time = datetime.timedelta(seconds=seconds)
 
+    def incUsers(self):
+        self.m_num_users += 1
+
+    def decUsers(self):
+        self.m_num_users -= 1
+
+        if self.m_num_users == 0:
+            self.noUsersLeft()
+
+    def noUsersLeft(self):
+        """Can be overriden by child classes to react on the last open file
+        description being gone for this node."""
+        pass
+
     def getStat(self):
         return self.m_stat
 
@@ -174,7 +189,7 @@
     def isRoot(self):
         """Returns whether this node is the root node of the file
         system."""
-        return self.m_parent is not None
+        return self.m_parent is None
 
     def getRoot(self):
         """Returns the root node of the file system."""
@@ -224,9 +239,9 @@
             try:
                 self.update()
             except Exception as e:
-                print("Failed to update {}: {}".format(
+                print("Failed to update {}:\n{}".format(
                     self.getName(),
-                    oscfs.misc.getFriendlyException(e),
+                    oscfs.misc.getExceptionTrace(e),
                 ), file=sys.stderr)
                 raise fuse.FuseOSError(errno.EFAULT)
             self.setCacheFresh()
@@ -255,6 +270,9 @@
         if date:
             stat.setModTime(date)
 
+    def dropCache(self):
+        self.m_content = None
+
     def setUseCache(self, on_off):
         self.m_use_cache = on_off
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/urlopenwrapper.py 
new/oscfs-0.9.0/oscfs/urlopenwrapper.py
--- old/oscfs-v0.8.1/oscfs/urlopenwrapper.py    2022-02-23 11:16:38.000000000 
+0100
+++ new/oscfs-0.9.0/oscfs/urlopenwrapper.py     2023-10-20 11:03:57.000000000 
+0200
@@ -55,13 +55,25 @@
 
         # add user/password, but only if it's an encrypted connection
         if proto == 'https' and api_conf:
-            userpw = "{}:{}".format(
-                api_conf["user"], api_conf["pass"]
-            )
-            auth = "Basic {}".format(
-                base64.b64encode(userpw.encode()).decode()
-            )
-            req.add_unredirected_header("Authorization", auth)
+            if "sshkey" in api_conf and api_conf["sshkey"] is not None:
+                raise Exception("Cannot use urlopenwrapper hack, because 
sshkey usage is configured for authentication. Consider using 
--no-urlopen-wrapper.")
+            else:
+                # the reason that we have to mimic the header authorization
+                # code from the osc module is that the osc module uses a
+                # global urllib2 opener handler mechanism that is pretty
+                # complicated ... normally after getting a HTTP 401 error this
+                # opener handler would only lazily add the required
+                # authorization parameters. I didn't find a simple way to call
+                # into this logic from this wrapper here yet. This also means
+                # that the new SSH key 2 factor authentication breaks when
+                # using this wrapper.
+                userpw = "{}:{}".format(
+                    api_conf["user"], api_conf["pass"]
+                )
+                auth = "Basic {}".format(
+                    base64.b64encode(userpw.encode()).decode()
+                )
+                req.add_unredirected_header("Authorization", auth)
 
         connection = self._getConnection(proto, host)
         retries = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oscfs-v0.8.1/tests/regtest.py 
new/oscfs-0.9.0/tests/regtest.py
--- old/oscfs-v0.8.1/tests/regtest.py   2022-02-23 11:16:38.000000000 +0100
+++ new/oscfs-0.9.0/tests/regtest.py    2023-10-20 11:03:57.000000000 +0200
@@ -37,6 +37,13 @@
         import osc.conf
         # determines the active oscrc configuration file
         self.m_oscrc_config = os.path.expanduser(osc.conf.identify_conf())
+        try:
+            options = osc.conf.Options()
+            # determines the active cookie jar file which caches authentication
+            self.m_oscrc_cookiejar = os.path.expanduser(options.cookiejar)
+        except AttributeError:
+            # in older OSC versions this was only available this way
+            self.m_oscrc_cookiejar = 
os.path.expanduser(osc.conf._identify_osccookiejar())
 
     def _lookupOscFsBin(self):
 
@@ -102,6 +109,12 @@
         # could cause a blocking child process when the pipe is full
         # ...
 
+    def _fuserumount(self):
+        """Uses fusermount to unmount a potential existing oscfs mount. This
+        is useful for background instances of oscfs. Errors are not reported"""
+
+        subprocess.call(["fusermount", "-u", self.m_mnt_dir])
+
     def umount(self):
 
         if not self.m_oscfs_proc:
@@ -217,6 +230,12 @@
 """,
                 file=oscrc)
 
+        # make sure any cached authentication is also removed for this test
+        try:
+            os.remove(self.m_oscrc_cookiejar)
+        except Exception:
+            pass
+
         # this is actually more complex, osc chokes on various other
         # conditions like config file not being there, config for apiurl
         # missing etc.
@@ -240,6 +259,9 @@
             self.m_oscfs_proc = None
 
             if res != 1 or not found_auth_error:
+                # it seems oscfs runs in the background now, explicitly
+                # unmount via fusermount
+                self._fuserumount()
                 raise Exception("No authentication error code or message was 
reported: code = {}".format(res))
         finally:
             self._restoreOscConfig()

Reply via email to