From 4ec8fc5cc86c36dc162882eca7b84534f2a6534f Mon Sep 17 00:00:00 2001
From: Mike McLean <mikem@redhat.com>
Date: May 08 2024 18:10:18 +0000
Subject: [PATCH 1/2] handle volumes when clearing stray build dirs


---

diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py
index d060484..024f047 100644
--- a/kojihub/kojihub.py
+++ b/kojihub/kojihub.py
@@ -6221,9 +6221,20 @@ def recycle_build(old, data):
         update.set(cg_id=data['cg_id'])
     update.rawset(create_event='get_event()')
     update.execute()
-    builddir = koji.pathinfo.build(data)
-    if os.path.exists(builddir):
-        koji.util.rmtree(builddir)
+
+    # delete stray files
+    for check_vol in list_volumes():
+        check_binfo = data.copy()
+        check_binfo['volume_id'] = check_vol['id']
+        check_binfo['volume_name'] = check_vol['name']
+        checkdir = koji.pathinfo.build(check_binfo)
+        if os.path.islink(checkdir):
+            logger.warning(f'Removing stray build symlink: {checkdir}')
+            os.unlink(checkdir)
+        elif os.path.exists(checkdir):
+            logger.warning(f'Removing stray build directory: {checkdir}')
+            koji.util.rmtree(checkdir)
+
     buildinfo = get_build(data['id'], strict=True)
     koji.plugin.run_callbacks('postBuildStateChange', attribute='state',
                               old=old['state'], new=data['state'], info=buildinfo)

From c2c68ef2a0bd96723ebcf64daebeeac6e953e44a Mon Sep 17 00:00:00 2001
From: Mike McLean <mikem@redhat.com>
Date: May 08 2024 18:39:16 +0000
Subject: [PATCH 2/2] unit test


---

diff --git a/tests/test_hub/test_recycle_build.py b/tests/test_hub/test_recycle_build.py
index 24d3704..dcc5eb0 100644
--- a/tests/test_hub/test_recycle_build.py
+++ b/tests/test_hub/test_recycle_build.py
@@ -20,12 +20,16 @@ class TestRecycleBuild(unittest.TestCase):
                                           side_effect=self.getUpdate).start()
         self.run_callbacks = mock.patch('koji.plugin.run_callbacks').start()
         self.rmtree = mock.patch('koji.util.rmtree').start()
-        self.exists = mock.patch('os.path.exists').start()
+        self.unlink = mock.patch('os.unlink').start()
+        self.islink = mock.patch('os.path.islink', return_value=False).start()
+        self.exists = mock.patch('os.path.exists', return_value=False).start()
         self.updates = []
         self.DeleteProcessor = mock.patch('kojihub.kojihub.DeleteProcessor',
                                           side_effect=self.getDelete).start()
         self.deletes = []
         self.get_build = mock.patch('kojihub.kojihub.get_build').start()
+        self.list_volumes = mock.patch('kojihub.kojihub.list_volumes').start()
+        self.list_volumes.return_value = [{'id': 0, 'name': 'DEFAULT'}]
 
     def tearDown(self):
         mock.patch.stopall()
@@ -276,3 +280,36 @@ class TestRecycleBuild(unittest.TestCase):
 
         self.get_build.assert_called_once_with(new['id'], strict=True)
         self.assertEqual(self.run_callbacks.call_count, 2)
+
+        # our default data does not include stray files
+        self.rmtree.assert_not_called()
+        self.unlink.assert_not_called()
+
+    def test_stray_link(self):
+        old = self.old.copy()
+        new = self.new.copy()
+        old['task_id'] = new['task_id'] = 137
+        self.query_execute.side_effect = [[], [], []]
+        self.get_build.return_value = {'build_id': 2, 'name': 'GConf2', 'version': '3.2.6',
+                                       'release': '15.fc23'}
+
+        self.islink.return_value = True
+        kojihub.recycle_build(old, new)
+        self.rmtree.assert_not_called()
+        self.unlink.assert_called_once_with('/mnt/koji/packages/GConf2/3.2.6/15.fc23')
+
+    def test_stray_dir(self):
+        old = self.old.copy()
+        new = self.new.copy()
+        old['task_id'] = new['task_id'] = 137
+        self.query_execute.side_effect = [[], [], []]
+        self.get_build.return_value = {'build_id': 2, 'name': 'GConf2', 'version': '3.2.6',
+                                       'release': '15.fc23'}
+
+        self.exists.return_value = True
+        kojihub.recycle_build(old, new)
+        self.unlink.assert_not_called()
+        self.rmtree.assert_called_once_with('/mnt/koji/packages/GConf2/3.2.6/15.fc23')
+
+
+# the end

