Repository: cobbler/cobbler

Issue:
[Backport] `cobbler buildiso` fails when a system is based on an image
### Original feature issue

- Issue: #3236
- PR: #3237

### Target release

- [x] release33
- [ ] release32
- [ ] release30

### Reason

Stabilization of Cobbler 3.3.3 for Uyuni and SUSE Manager.



Discussion:

Resolving patch:
diff --git a/cobbler/actions/buildiso/netboot.py b/cobbler/actions/buildiso/netboot.py
--- a/cobbler/actions/buildiso/netboot.py
+++ b/cobbler/actions/buildiso/netboot.py
@@ -431,7 +431,15 @@ def filter_systems(self, selected_items: List[str] = None) -> list:
         """
         if selected_items is None:
             selected_items = []
-        return self.filter_items(self.api.systems(), selected_items)
+        found_systems = self.filter_items(self.api.systems(), selected_items)
+        # Now filter all systems out that are image based as we don't know about their kernel and initrds
+        return_systems = []
+        for system in found_systems:
+            # All systems not underneath a profile should be skipped
+            if system.get_conceptual_parent().TYPE_NAME == "profile":
+                return_systems.append(system)
+        # Now finally return
+        return return_systems
 
     def make_shorter(self, distname: str) -> str:
         """


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,6 +9,7 @@
 from cobbler.items.distro import Distro
 from cobbler.items.profile import Profile
 from cobbler.items.system import NetworkInterface, System
+from cobbler.items.image import Image
 
 
 @contextmanager
@@ -37,7 +38,7 @@ def reset_items(cobbler_api):
     for system in cobbler_api.systems():
         cobbler_api.remove_system(system.name)
     for image in cobbler_api.images():
-        cobbler_api.remove_distro(image.name)
+        cobbler_api.remove_image(image.name)
     for profile in cobbler_api.profiles():
         cobbler_api.remove_profile(profile.name)
     for distro in cobbler_api.distros():
@@ -108,6 +109,22 @@ def _create_profile(distro_name):
     return _create_profile
 
 
+@pytest.fixture(scope="function")
+def create_image(request, cobbler_api):
+    """
+    Returns a function which has no arguments. The function returns an image object. The image is already added to the
+    CobblerAPI.
+    """
+
+    def _create_image():
+        test_image = Image(cobbler_api)
+        test_image.name = request.node.originalname
+        cobbler_api.add_image(test_image)
+        return test_image
+
+    return _create_image
+
+
 @pytest.fixture(scope="function")
 def create_system(request, cobbler_api):
     """
@@ -115,9 +132,12 @@ def create_system(request, cobbler_api):
     already added to the CobblerAPI.
     """
 
-    def _create_system(profile_name="", image_name=""):
+    def _create_system(profile_name="", image_name="", name=""):
         test_system = System(cobbler_api)
-        test_system.name = request.node.originalname
+        if name == "":
+            test_system.name = request.node.originalname
+        else:
+            test_system.name = name
         if profile_name != "":
             test_system.profile = profile_name
         if image_name != "":
diff --git a/tests/xmlrpcapi/image_test.py b/tests/xmlrpcapi/image_test.py
--- a/tests/xmlrpcapi/image_test.py
+++ b/tests/xmlrpcapi/image_test.py
@@ -1,45 +1,22 @@
-import pytest
-
-# TODO: Create fixture where image is create
-
-
-@pytest.fixture(scope="function")
-def remove_item(remote, token):
-    """
-    Remove an item with the given name.
-
-    :param token: The fixture to have the token for authenticated strings available.
-    :param remote: The fixture to have the base xmlrpc connection.
-    """
-    def _remove_item(itemtype, name):
-        yield
-        remote.remove_item(itemtype, name, token)
-    return _remove_item
-
-
 class TestImage:
 
     def test_create_image(self, remote, token):
         """
-        Test: create/edit of an image object"""
-
-        # Arrange
-
+        Test: create/edit of an image object
+        """
         # Act
-        images = remote.get_images(token)
         image = remote.new_image(token)
 
         # Assert
         assert remote.modify_image(image, "name", "testimage0", token)
         assert remote.save_image(image, token)
-        new_images = remote.get_images(token)
-        assert len(new_images) == len(images) + 1
+        image_list = remote.get_images(token)
+        assert len(image_list) == 1
 
     def test_get_images(self, remote):
         """
         Test: get images
         """
-
         # Arrange
 
         # Act
@@ -47,68 +24,72 @@ def test_get_images(self, remote):
 
         # Assert
 
-    def test_get_image(self, remote):
+    def test_get_image(self, remote, create_image):
         """
         Test: Get an image object
         """
-
         # Arrange
+        test_image = create_image()
 
         # Act
+        result_image = remote.get_image(test_image.name)
 
         # Assert
-        image = remote.get_image("testimage0")
+        assert result_image.get("name") == test_image.name
 
-    def test_find_image(self, remote, token):
+    def test_find_image(self, remote, token, create_image):
         """
         Test: Find an image object
         """
-
         # Arrange
+        test_image = create_image()
 
         # Act
-        result = remote.find_image({"name": "testimage0"}, token)
+        result = remote.find_image({"name": test_image.name}, token)
 
-        # Assert
-        assert result
+        # Assert - We want to find exactly the one item we added
+        assert len(result) == 1
+        assert result[0].get("name") == test_image.name
 
-    def test_copy_image(self, remote, token):
+    def test_copy_image(self, remote, token, create_image):
         """
         Test: Copy an image object
         """
-
         # Arrange
+        test_image = create_image()
+        new_name = "testimagecopy"
+        image = remote.get_item_handle("image", test_image.name, token)
 
         # Act
-        image = remote.get_item_handle("image", "testimage0", token)
+        result = remote.copy_image(image, new_name, token)
 
         # Assert
-        assert remote.copy_image(image, "testimagecopy", token)
+        assert result
 
-    def test_rename_image(self, remote, token, remove_item):
+    def test_rename_image(self, remote, token, create_image):
         """
         Test: Rename an image object
         """
         # Arrange
-        name = "testimage1"
-        image = remote.get_item_handle("image", "testimagecopy", token)
+        test_image = create_image()
+        new_name = "testimage_renamed"
+        image = remote.get_item_handle("image", test_image.name, token)
 
         # Act
-        result = remote.rename_image(image, name, token)
-
-        # Cleanup
-        remote.remove_item("image", name, token)
+        result = remote.rename_image(image, new_name, token)
 
         # Assert
         assert result
 
-    def test_remove_image(self, remote, token):
+    def test_remove_image(self, remote, token, create_image):
         """
         Test: remove an image object
         """
         # Arrange
+        test_image = create_image()
 
         # Act
+        result = remote.remove_image(test_image.name, token)
 
         # Assert
-        assert remote.remove_image("testimage0", token)
+        assert result