Repository: cobbler/cobbler

Issue:
Add indicators for grub distro level menus
### Is your feature request related to a problem?

This issue is related to https://github.com/cobbler/cobbler/pull/2217

Currently Grub does not indicate that the menu structure contains submenus. Thus we desire that because not everyone knows that we are using submenus now.

### Provide a detailed description of the proposed feature

An indicator in the menu which shows that this is not a boot entry.

### Alternatives you've considered

- Letting users discover that for themselves: Well not user friendly
- Reverting the change: Not good as well since this can cause a lot of overhead in the boot menu.

### Additional information

Internal Bug for SUSE Employees: https://github.com/SUSE/spacewalk/issues/15241



Discussion:
This was refactored with https://github.com/cobbler/cobbler/pull/2575 so sadly the easy way we had then doesn't work now. I will investigate deeper.
This feature request was raised against Cobbler version 3.1.2 since that is was SUMA uses atm. Since the refactoring by @tpw56j was done, I need to reproduce that on `master` now.

@tpw56j do you have an idea how to add an indicator. I saw your code and was wondering where the most top level menu is generated for all loaders? In `make_pxe_menu` maybe but I am not sure right now.
As written in #2684 I found that the children list has invalid entries and thus i need to dive deeper there first.

Resolving patch:
diff --git a/cobbler/tftpgen.py b/cobbler/tftpgen.py
--- a/cobbler/tftpgen.py
+++ b/cobbler/tftpgen.py
@@ -356,9 +356,8 @@ def make_pxe_menu(self):
             if 'grub' in arch_menu_items:
                 boot_menu["grub"] = arch_menu_items
                 outfile = os.path.join(self.bootloc, "grub", "{0}_menu_items.cfg".format(arch))
-                fd = open(outfile, "w+")
-                fd.write(arch_menu_items["grub"])
-                fd.close()
+                with open(outfile, "w+") as fd:
+                    fd.write(arch_menu_items["grub"])
         return boot_menu
 
     def get_menu_items(self, arch: Optional[str] = None) -> dict:
@@ -531,9 +530,8 @@ def get_menu_level(self, menu=None, arch: str = None) -> dict:
         for boot_loader in boot_loaders:
             template = os.path.join(self.settings.boot_loader_conf_template_dir, "%s_submenu.template" % boot_loader)
             if os.path.exists(template):
-                template_fh = open(template)
-                template_data[boot_loader] = template_fh.read()
-                template_fh.close()
+                with open(template) as template_fh:
+                    template_data[boot_loader] = template_fh.read()
                 if menu:
                     parent_menu = menu.parent
                     metadata["menu_name"] = menu.name
@@ -548,6 +546,9 @@ def get_menu_level(self, menu=None, arch: str = None) -> dict:
                     else:
                         metadata["parent_menu_name"] = "Cobbler"
                         metadata["parent menu_label"] = "Cobbler"
+            else:
+                self.logger.warning("Template for building a submenu not found for bootloader \"%s\"! Submenu "
+                                    "structure thus missing for this bootloader.", boot_loader)
 
         self.get_submenus(menu, metadata, arch)
         nested_menu_items = metadata["menu_items"]


Test patch: