We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
cobbler import rsync fails from mounted ISO media with SELinux enabled with return code 23
### Describe the bug

When you mount an ISO image - the permissions of the mounted files are read only:
```
# ls -la /mnt
total 38
dr-xr-xr-x.  1 root root  2048 Oct 29 22:06 ./
dr-xr-xr-x. 25 root root  4096 Oct 26 08:44 ../
dr-xr-xr-x.  1 root root  2048 Oct 29 22:06 AppStream/
dr-xr-xr-x.  1 root root  2048 Oct 29 22:06 BaseOS/
-r--r--r--.  1 root root    45 Oct 29 22:06 .discinfo
dr-xr-xr-x.  1 root root  2048 Oct 29 21:53 EFI/
-r--r--r--.  1 root root   299 Oct 29 22:06 EULA
-r--r--r--.  1 root root   745 Oct 29 22:06 extra_files.json
dr-xr-xr-x.  1 root root  2048 Oct 29 21:53 images/
dr-xr-xr-x.  1 root root  2048 Oct 29 21:53 isolinux/
-r--r--r--.  1 root root 18092 Oct 29 22:06 LICENSE
-r--r--r--.  1 root root    88 Oct 29 22:06 media.repo
-r--r--r--.  1 root root  1530 Oct 29 22:06 .treeinfo
```
When you run `cobbler import --path=/mnt` the rsync will fail:
```
running: ['rsync', '--archive', '--progress', '/mnt/', '/var/www/cobbler/distro_mirror/centos-stream-9']
received on stdout: sending incremental file list
Exception occurred: <class 'RuntimeError'>
Exception value: rsync import failed with return code 23!
Exception Info:
!!! TASK FAILED !!!
```

### Steps to reproduce

1. mount -o loop /path/to/ISO /mnt
2. cobbler import --path=/mnt

### Expected behavior

Distro is imported.

### Cobbler version

<!--- Paste output from `cobbler version` -->
````paste below
Cobbler 3.4.0
  source: ?, ?
  build time: Sat Nov  4 21:15:48 2023
````

### Operating system

CentOS Stream 8

### Cobbler log

<!--- Paste (partial) output from `/var/log/cobbler/cobbler.log` -->
````paste below
2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | importing from a network location, running rsync to fetch the files first
[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | running: ['rsync', '--archive', '--progress', '/tmp/Fedora-Server-x86_64-38-1.6.iso/', '/var/www/cobbler/distro_mirror/fedora-38']
[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | received on stdout: sending incremental file list

[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | Exception occurred: <class 'RuntimeError'>
[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | Exception value: rsync import failed with return code 23!
[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | Exception Info:
  File "/usr/lib/python3.6/site-packages/cobbler/utils/thread.py", line 103, in run
    return_code = self._run(self)

  File "/usr/lib/python3.6/site-packages/cobbler/remote.py", line 398, in runner
    self.options.get("os_version", None),

  File "/usr/lib/python3.6/site-packages/cobbler/api.py", line 2327, in import_tree
    os_version,

  File "/usr/lib/python3.6/site-packages/cobbler/actions/importer.py", line 127, in run
    f"rsync import failed with return code {rsync_return_code}!"

[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - ERROR | ### TASK FAILED ###
````

### Additional information

The problem is that the read-only permissions are copied to the destination, and then cobbler does not have permission to write to the destination without the `dac_override` permission which is not granted:
```
type=AVC msg=audit(1699229796.164:5238): avc:  denied  { dac_override } for  pid=142026 comm="rsync" capability=1  scontext=system_u:system_r:cobblerd_t:s0 tcontext=system_u:system_r:cobblerd_t:s0 tclass=capability permissive=0
```

I will be submitting a pull request the changes the rsync options to grant write permissions on the destination, which is what we would want anyway.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cobbler/actions/importer.py`
Content:
```
1 """
2 This module contains the logic that kicks of the ``cobbler import`` process. This is extracted logic from ``api.py``
3 that is essentially calling ``modules/mangers/import_signatures.py`` with some preparatory code.
4 """
5 import logging
6 import os
7 from typing import TYPE_CHECKING, Optional
8 
9 from cobbler import utils
10 from cobbler.utils import filesystem_helpers
11 
12 if TYPE_CHECKING:
13     from cobbler.api import CobblerAPI
14 
15 
16 class Importer:
17     """
18     Wrapper class to adhere to the style of all other actions.
19     """
20 
21     def __init__(self, api: "CobblerAPI") -> None:
22         """
23         Constructor to initialize the class.
24 
25         :param api: The CobblerAPI.
26         """
27         self.api = api
28         self.logger = logging.getLogger()
29 
30     def run(
31         self,
32         mirror_url: str,
33         mirror_name: str,
34         network_root: Optional[str] = None,
35         autoinstall_file: Optional[str] = None,
36         rsync_flags: Optional[str] = None,
37         arch: Optional[str] = None,
38         breed: Optional[str] = None,
39         os_version: Optional[str] = None,
40     ) -> bool:
41         """
42         Automatically import a directory tree full of distribution files.
43 
44         :param mirror_url: Can be a string that represents a path, a user@host syntax for SSH, or an rsync:// address.
45                            If mirror_url is a filesystem path and mirroring is not desired, set network_root to
46                            something like "nfs://path/to/mirror_url/root"
47         :param mirror_name: The name of the mirror.
48         :param network_root: the remote path (nfs/http/ftp) for the distro files
49         :param autoinstall_file: user-specified response file, which will override the default
50         :param rsync_flags: Additional flags that will be passed to the rsync call that will sync everything to the
51                             Cobbler webroot.
52         :param arch: user-specified architecture
53         :param breed: user-specified breed
54         :param os_version: user-specified OS version
55         """
56         self.api.log(
57             "import_tree",
58             [mirror_url, mirror_name, network_root, autoinstall_file, rsync_flags],
59         )
60 
61         # Both --path and --name are required arguments.
62         if mirror_url is None or not mirror_url:
63             self.logger.info("import failed.  no --path specified")
64             return False
65         if not mirror_name:
66             self.logger.info("import failed.  no --name specified")
67             return False
68 
69         path = os.path.normpath(
70             f"{self.api.settings().webdir}/distro_mirror/{mirror_name}"
71         )
72         if arch is not None:
73             arch = arch.lower()
74             if arch == "x86":
75                 # be consistent
76                 arch = "i386"
77             if path.split("-")[-1] != arch:
78                 path += f"-{arch}"
79 
80         # We need to mirror (copy) the files.
81         self.logger.info(
82             "importing from a network location, running rsync to fetch the files first"
83         )
84 
85         filesystem_helpers.mkdir(path)
86 
87         # Prevent rsync from creating the directory name twice if we are copying via rsync.
88 
89         if not mirror_url.endswith("/"):
90             mirror_url = f"{mirror_url}/"
91 
92         if (
93             mirror_url.startswith("http://")
94             or mirror_url.startswith("https://")
95             or mirror_url.startswith("ftp://")
96             or mirror_url.startswith("nfs://")
97         ):
98             # HTTP mirrors are kind of primitive. rsync is better. That's why this isn't documented in the manpage and
99             # we don't support them.
100             # TODO: how about adding recursive FTP as an option?
101             self.logger.info("unsupported protocol")
102             return False
103 
104         # Good, we're going to use rsync.. We don't use SSH for public mirrors and local files.
105         # Presence of user@host syntax means use SSH
106         spacer = ""
107         if not mirror_url.startswith("rsync://") and not mirror_url.startswith("/"):
108             spacer = ' -e "ssh" '
109         rsync_cmd = ["rsync", "--archive"]
110         if spacer != "":
111             rsync_cmd.append(spacer)
112         rsync_cmd.append("--progress")
113         if rsync_flags:
114             rsync_cmd.append(rsync_flags)
115 
116         # If --available-as was specified, limit the files we pull down via rsync to just those that are critical
117         # to detecting what the distro is
118         if network_root is not None:
119             rsync_cmd.append("--include-from=/etc/cobbler/import_rsync_whitelist")
120 
121         rsync_cmd += [mirror_url, path]
122 
123         # kick off the rsync now
124         rsync_return_code = utils.subprocess_call(rsync_cmd, shell=False)
125         if rsync_return_code != 0:
126             raise RuntimeError(
127                 f"rsync import failed with return code {rsync_return_code}!"
128             )
129 
130         if network_root is not None:
131             # In addition to mirroring, we're going to assume the path is available over http, ftp, and nfs, perhaps on
132             # an external filer. Scanning still requires --mirror is a filesystem path, but --available-as marks the
133             # network path. This allows users to point the path at a directory containing just the network boot files
134             # while the rest of the distro files are available somewhere else.
135 
136             # Find the filesystem part of the path, after the server bits, as each distro URL needs to be calculated
137             # relative to this.
138 
139             if not network_root.endswith("/"):
140                 network_root += "/"
141             valid_roots = ["nfs://", "ftp://", "http://", "https://"]
142             for valid_root in valid_roots:
143                 if network_root.startswith(valid_root):
144                     break
145             else:
146                 self.logger.info(
147                     "Network root given to --available-as must be nfs://, ftp://, http://, or https://"
148                 )
149                 return False
150 
151             if network_root.startswith("nfs://"):
152                 try:
153                     (_, _, _) = network_root.split(":", 3)
154                 except ValueError:
155                     self.logger.info(
156                         "Network root given to --available-as is missing a colon, please see the manpage example."
157                     )
158                     return False
159 
160         import_module = self.api.get_module_by_name("managers.import_signatures")
161         if import_module is None:
162             raise ImportError("Could not retrieve import signatures module!")
163         import_manager = import_module.get_import_manager(self.api)
164         import_manager.run(
165             path, mirror_name, network_root, autoinstall_file, arch, breed, os_version
166         )
167         return True
168
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cobbler/actions/importer.py b/cobbler/actions/importer.py
--- a/cobbler/actions/importer.py
+++ b/cobbler/actions/importer.py
@@ -106,7 +106,8 @@
         spacer = ""
         if not mirror_url.startswith("rsync://") and not mirror_url.startswith("/"):
             spacer = ' -e "ssh" '
-        rsync_cmd = ["rsync", "--archive"]
+        # --archive but without -p to avoid copying read-only ISO permissions and making sure we have write access
+        rsync_cmd = ["rsync", "-rltgoD", "--chmod=ug=rwX"]
         if spacer != "":
             rsync_cmd.append(spacer)
         rsync_cmd.append("--progress")

{"golden_diff": "diff --git a/cobbler/actions/importer.py b/cobbler/actions/importer.py\n--- a/cobbler/actions/importer.py\n+++ b/cobbler/actions/importer.py\n@@ -106,7 +106,8 @@\n         spacer = \"\"\n         if not mirror_url.startswith(\"rsync://\") and not mirror_url.startswith(\"/\"):\n             spacer = ' -e \"ssh\" '\n-        rsync_cmd = [\"rsync\", \"--archive\"]\n+        # --archive but without -p to avoid copying read-only ISO permissions and making sure we have write access\n+        rsync_cmd = [\"rsync\", \"-rltgoD\", \"--chmod=ug=rwX\"]\n         if spacer != \"\":\n             rsync_cmd.append(spacer)\n         rsync_cmd.append(\"--progress\")\n", "issue": "cobbler import rsync fails from mounted ISO media with SELinux enabled with return code 23\n### Describe the bug\r\n\r\nWhen you mount an ISO image - the permissions of the mounted files are read only:\r\n```\r\n# ls -la /mnt\r\ntotal 38\r\ndr-xr-xr-x.  1 root root  2048 Oct 29 22:06 ./\r\ndr-xr-xr-x. 25 root root  4096 Oct 26 08:44 ../\r\ndr-xr-xr-x.  1 root root  2048 Oct 29 22:06 AppStream/\r\ndr-xr-xr-x.  1 root root  2048 Oct 29 22:06 BaseOS/\r\n-r--r--r--.  1 root root    45 Oct 29 22:06 .discinfo\r\ndr-xr-xr-x.  1 root root  2048 Oct 29 21:53 EFI/\r\n-r--r--r--.  1 root root   299 Oct 29 22:06 EULA\r\n-r--r--r--.  1 root root   745 Oct 29 22:06 extra_files.json\r\ndr-xr-xr-x.  1 root root  2048 Oct 29 21:53 images/\r\ndr-xr-xr-x.  1 root root  2048 Oct 29 21:53 isolinux/\r\n-r--r--r--.  1 root root 18092 Oct 29 22:06 LICENSE\r\n-r--r--r--.  1 root root    88 Oct 29 22:06 media.repo\r\n-r--r--r--.  1 root root  1530 Oct 29 22:06 .treeinfo\r\n```\r\nWhen you run `cobbler import --path=/mnt` the rsync will fail:\r\n```\r\nrunning: ['rsync', '--archive', '--progress', '/mnt/', '/var/www/cobbler/distro_mirror/centos-stream-9']\r\nreceived on stdout: sending incremental file list\r\nException occurred: <class 'RuntimeError'>\r\nException value: rsync import failed with return code 23!\r\nException Info:\r\n!!! TASK FAILED !!!\r\n```\r\n\r\n### Steps to reproduce\r\n\r\n1. mount -o loop /path/to/ISO /mnt\r\n2. cobbler import --path=/mnt\r\n\r\n### Expected behavior\r\n\r\nDistro is imported.\r\n\r\n### Cobbler version\r\n\r\n<!--- Paste output from `cobbler version` -->\r\n````paste below\r\nCobbler 3.4.0\r\n  source: ?, ?\r\n  build time: Sat Nov  4 21:15:48 2023\r\n````\r\n\r\n### Operating system\r\n\r\nCentOS Stream 8\r\n\r\n### Cobbler log\r\n\r\n<!--- Paste (partial) output from `/var/log/cobbler/cobbler.log` -->\r\n````paste below\r\n2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | importing from a network location, running rsync to fetch the files first\r\n[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | running: ['rsync', '--archive', '--progress', '/tmp/Fedora-Server-x86_64-38-1.6.iso/', '/var/www/cobbler/distro_mirror/fedora-38']\r\n[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | received on stdout: sending incremental file list\r\n\r\n[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | Exception occurred: <class 'RuntimeError'>\r\n[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | Exception value: rsync import failed with return code 23!\r\n[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - INFO | Exception Info:\r\n  File \"/usr/lib/python3.6/site-packages/cobbler/utils/thread.py\", line 103, in run\r\n    return_code = self._run(self)\r\n\r\n  File \"/usr/lib/python3.6/site-packages/cobbler/remote.py\", line 398, in runner\r\n    self.options.get(\"os_version\", None),\r\n\r\n  File \"/usr/lib/python3.6/site-packages/cobbler/api.py\", line 2327, in import_tree\r\n    os_version,\r\n\r\n  File \"/usr/lib/python3.6/site-packages/cobbler/actions/importer.py\", line 127, in run\r\n    f\"rsync import failed with return code {rsync_return_code}!\"\r\n\r\n[2023-11-04_160526_Media import_8a52b82c5b54433587b15bc9e9d2e21f] 2023-11-04T16:05:26 - ERROR | ### TASK FAILED ###\r\n````\r\n\r\n### Additional information\r\n\r\nThe problem is that the read-only permissions are copied to the destination, and then cobbler does not have permission to write to the destination without the `dac_override` permission which is not granted:\r\n```\r\ntype=AVC msg=audit(1699229796.164:5238): avc:  denied  { dac_override } for  pid=142026 comm=\"rsync\" capability=1  scontext=system_u:system_r:cobblerd_t:s0 tcontext=system_u:system_r:cobblerd_t:s0 tclass=capability permissive=0\r\n```\r\n\r\nI will be submitting a pull request the changes the rsync options to grant write permissions on the destination, which is what we would want anyway.\n", "before_files": [{"content": "\"\"\"\nThis module contains the logic that kicks of the ``cobbler import`` process. This is extracted logic from ``api.py``\nthat is essentially calling ``modules/mangers/import_signatures.py`` with some preparatory code.\n\"\"\"\nimport logging\nimport os\nfrom typing import TYPE_CHECKING, Optional\n\nfrom cobbler import utils\nfrom cobbler.utils import filesystem_helpers\n\nif TYPE_CHECKING:\n    from cobbler.api import CobblerAPI\n\n\nclass Importer:\n    \"\"\"\n    Wrapper class to adhere to the style of all other actions.\n    \"\"\"\n\n    def __init__(self, api: \"CobblerAPI\") -> None:\n        \"\"\"\n        Constructor to initialize the class.\n\n        :param api: The CobblerAPI.\n        \"\"\"\n        self.api = api\n        self.logger = logging.getLogger()\n\n    def run(\n        self,\n        mirror_url: str,\n        mirror_name: str,\n        network_root: Optional[str] = None,\n        autoinstall_file: Optional[str] = None,\n        rsync_flags: Optional[str] = None,\n        arch: Optional[str] = None,\n        breed: Optional[str] = None,\n        os_version: Optional[str] = None,\n    ) -> bool:\n        \"\"\"\n        Automatically import a directory tree full of distribution files.\n\n        :param mirror_url: Can be a string that represents a path, a user@host syntax for SSH, or an rsync:// address.\n                           If mirror_url is a filesystem path and mirroring is not desired, set network_root to\n                           something like \"nfs://path/to/mirror_url/root\"\n        :param mirror_name: The name of the mirror.\n        :param network_root: the remote path (nfs/http/ftp) for the distro files\n        :param autoinstall_file: user-specified response file, which will override the default\n        :param rsync_flags: Additional flags that will be passed to the rsync call that will sync everything to the\n                            Cobbler webroot.\n        :param arch: user-specified architecture\n        :param breed: user-specified breed\n        :param os_version: user-specified OS version\n        \"\"\"\n        self.api.log(\n            \"import_tree\",\n            [mirror_url, mirror_name, network_root, autoinstall_file, rsync_flags],\n        )\n\n        # Both --path and --name are required arguments.\n        if mirror_url is None or not mirror_url:\n            self.logger.info(\"import failed.  no --path specified\")\n            return False\n        if not mirror_name:\n            self.logger.info(\"import failed.  no --name specified\")\n            return False\n\n        path = os.path.normpath(\n            f\"{self.api.settings().webdir}/distro_mirror/{mirror_name}\"\n        )\n        if arch is not None:\n            arch = arch.lower()\n            if arch == \"x86\":\n                # be consistent\n                arch = \"i386\"\n            if path.split(\"-\")[-1] != arch:\n                path += f\"-{arch}\"\n\n        # We need to mirror (copy) the files.\n        self.logger.info(\n            \"importing from a network location, running rsync to fetch the files first\"\n        )\n\n        filesystem_helpers.mkdir(path)\n\n        # Prevent rsync from creating the directory name twice if we are copying via rsync.\n\n        if not mirror_url.endswith(\"/\"):\n            mirror_url = f\"{mirror_url}/\"\n\n        if (\n            mirror_url.startswith(\"http://\")\n            or mirror_url.startswith(\"https://\")\n            or mirror_url.startswith(\"ftp://\")\n            or mirror_url.startswith(\"nfs://\")\n        ):\n            # HTTP mirrors are kind of primitive. rsync is better. That's why this isn't documented in the manpage and\n            # we don't support them.\n            # TODO: how about adding recursive FTP as an option?\n            self.logger.info(\"unsupported protocol\")\n            return False\n\n        # Good, we're going to use rsync.. We don't use SSH for public mirrors and local files.\n        # Presence of user@host syntax means use SSH\n        spacer = \"\"\n        if not mirror_url.startswith(\"rsync://\") and not mirror_url.startswith(\"/\"):\n            spacer = ' -e \"ssh\" '\n        rsync_cmd = [\"rsync\", \"--archive\"]\n        if spacer != \"\":\n            rsync_cmd.append(spacer)\n        rsync_cmd.append(\"--progress\")\n        if rsync_flags:\n            rsync_cmd.append(rsync_flags)\n\n        # If --available-as was specified, limit the files we pull down via rsync to just those that are critical\n        # to detecting what the distro is\n        if network_root is not None:\n            rsync_cmd.append(\"--include-from=/etc/cobbler/import_rsync_whitelist\")\n\n        rsync_cmd += [mirror_url, path]\n\n        # kick off the rsync now\n        rsync_return_code = utils.subprocess_call(rsync_cmd, shell=False)\n        if rsync_return_code != 0:\n            raise RuntimeError(\n                f\"rsync import failed with return code {rsync_return_code}!\"\n            )\n\n        if network_root is not None:\n            # In addition to mirroring, we're going to assume the path is available over http, ftp, and nfs, perhaps on\n            # an external filer. Scanning still requires --mirror is a filesystem path, but --available-as marks the\n            # network path. This allows users to point the path at a directory containing just the network boot files\n            # while the rest of the distro files are available somewhere else.\n\n            # Find the filesystem part of the path, after the server bits, as each distro URL needs to be calculated\n            # relative to this.\n\n            if not network_root.endswith(\"/\"):\n                network_root += \"/\"\n            valid_roots = [\"nfs://\", \"ftp://\", \"http://\", \"https://\"]\n            for valid_root in valid_roots:\n                if network_root.startswith(valid_root):\n                    break\n            else:\n                self.logger.info(\n                    \"Network root given to --available-as must be nfs://, ftp://, http://, or https://\"\n                )\n                return False\n\n            if network_root.startswith(\"nfs://\"):\n                try:\n                    (_, _, _) = network_root.split(\":\", 3)\n                except ValueError:\n                    self.logger.info(\n                        \"Network root given to --available-as is missing a colon, please see the manpage example.\"\n                    )\n                    return False\n\n        import_module = self.api.get_module_by_name(\"managers.import_signatures\")\n        if import_module is None:\n            raise ImportError(\"Could not retrieve import signatures module!\")\n        import_manager = import_module.get_import_manager(self.api)\n        import_manager.run(\n            path, mirror_name, network_root, autoinstall_file, arch, breed, os_version\n        )\n        return True\n", "path": "cobbler/actions/importer.py"}], "after_files": [{"content": "\"\"\"\nThis module contains the logic that kicks of the ``cobbler import`` process. This is extracted logic from ``api.py``\nthat is essentially calling ``modules/mangers/import_signatures.py`` with some preparatory code.\n\"\"\"\nimport logging\nimport os\nfrom typing import TYPE_CHECKING, Optional\n\nfrom cobbler import utils\nfrom cobbler.utils import filesystem_helpers\n\nif TYPE_CHECKING:\n    from cobbler.api import CobblerAPI\n\n\nclass Importer:\n    \"\"\"\n    Wrapper class to adhere to the style of all other actions.\n    \"\"\"\n\n    def __init__(self, api: \"CobblerAPI\") -> None:\n        \"\"\"\n        Constructor to initialize the class.\n\n        :param api: The CobblerAPI.\n        \"\"\"\n        self.api = api\n        self.logger = logging.getLogger()\n\n    def run(\n        self,\n        mirror_url: str,\n        mirror_name: str,\n        network_root: Optional[str] = None,\n        autoinstall_file: Optional[str] = None,\n        rsync_flags: Optional[str] = None,\n        arch: Optional[str] = None,\n        breed: Optional[str] = None,\n        os_version: Optional[str] = None,\n    ) -> bool:\n        \"\"\"\n        Automatically import a directory tree full of distribution files.\n\n        :param mirror_url: Can be a string that represents a path, a user@host syntax for SSH, or an rsync:// address.\n                           If mirror_url is a filesystem path and mirroring is not desired, set network_root to\n                           something like \"nfs://path/to/mirror_url/root\"\n        :param mirror_name: The name of the mirror.\n        :param network_root: the remote path (nfs/http/ftp) for the distro files\n        :param autoinstall_file: user-specified response file, which will override the default\n        :param rsync_flags: Additional flags that will be passed to the rsync call that will sync everything to the\n                            Cobbler webroot.\n        :param arch: user-specified architecture\n        :param breed: user-specified breed\n        :param os_version: user-specified OS version\n        \"\"\"\n        self.api.log(\n            \"import_tree\",\n            [mirror_url, mirror_name, network_root, autoinstall_file, rsync_flags],\n        )\n\n        # Both --path and --name are required arguments.\n        if mirror_url is None or not mirror_url:\n            self.logger.info(\"import failed.  no --path specified\")\n            return False\n        if not mirror_name:\n            self.logger.info(\"import failed.  no --name specified\")\n            return False\n\n        path = os.path.normpath(\n            f\"{self.api.settings().webdir}/distro_mirror/{mirror_name}\"\n        )\n        if arch is not None:\n            arch = arch.lower()\n            if arch == \"x86\":\n                # be consistent\n                arch = \"i386\"\n            if path.split(\"-\")[-1] != arch:\n                path += f\"-{arch}\"\n\n        # We need to mirror (copy) the files.\n        self.logger.info(\n            \"importing from a network location, running rsync to fetch the files first\"\n        )\n\n        filesystem_helpers.mkdir(path)\n\n        # Prevent rsync from creating the directory name twice if we are copying via rsync.\n\n        if not mirror_url.endswith(\"/\"):\n            mirror_url = f\"{mirror_url}/\"\n\n        if (\n            mirror_url.startswith(\"http://\")\n            or mirror_url.startswith(\"https://\")\n            or mirror_url.startswith(\"ftp://\")\n            or mirror_url.startswith(\"nfs://\")\n        ):\n            # HTTP mirrors are kind of primitive. rsync is better. That's why this isn't documented in the manpage and\n            # we don't support them.\n            # TODO: how about adding recursive FTP as an option?\n            self.logger.info(\"unsupported protocol\")\n            return False\n\n        # Good, we're going to use rsync.. We don't use SSH for public mirrors and local files.\n        # Presence of user@host syntax means use SSH\n        spacer = \"\"\n        if not mirror_url.startswith(\"rsync://\") and not mirror_url.startswith(\"/\"):\n            spacer = ' -e \"ssh\" '\n        # --archive but without -p to avoid copying read-only ISO permissions and making sure we have write access\n        rsync_cmd = [\"rsync\", \"-rltgoD\", \"--chmod=ug=rwX\"]\n        if spacer != \"\":\n            rsync_cmd.append(spacer)\n        rsync_cmd.append(\"--progress\")\n        if rsync_flags:\n            rsync_cmd.append(rsync_flags)\n\n        # If --available-as was specified, limit the files we pull down via rsync to just those that are critical\n        # to detecting what the distro is\n        if network_root is not None:\n            rsync_cmd.append(\"--include-from=/etc/cobbler/import_rsync_whitelist\")\n\n        rsync_cmd += [mirror_url, path]\n\n        # kick off the rsync now\n        rsync_return_code = utils.subprocess_call(rsync_cmd, shell=False)\n        if rsync_return_code != 0:\n            raise RuntimeError(\n                f\"rsync import failed with return code {rsync_return_code}!\"\n            )\n\n        if network_root is not None:\n            # In addition to mirroring, we're going to assume the path is available over http, ftp, and nfs, perhaps on\n            # an external filer. Scanning still requires --mirror is a filesystem path, but --available-as marks the\n            # network path. This allows users to point the path at a directory containing just the network boot files\n            # while the rest of the distro files are available somewhere else.\n\n            # Find the filesystem part of the path, after the server bits, as each distro URL needs to be calculated\n            # relative to this.\n\n            if not network_root.endswith(\"/\"):\n                network_root += \"/\"\n            valid_roots = [\"nfs://\", \"ftp://\", \"http://\", \"https://\"]\n            for valid_root in valid_roots:\n                if network_root.startswith(valid_root):\n                    break\n            else:\n                self.logger.info(\n                    \"Network root given to --available-as must be nfs://, ftp://, http://, or https://\"\n                )\n                return False\n\n            if network_root.startswith(\"nfs://\"):\n                try:\n                    (_, _, _) = network_root.split(\":\", 3)\n                except ValueError:\n                    self.logger.info(\n                        \"Network root given to --available-as is missing a colon, please see the manpage example.\"\n                    )\n                    return False\n\n        import_module = self.api.get_module_by_name(\"managers.import_signatures\")\n        if import_module is None:\n            raise ImportError(\"Could not retrieve import signatures module!\")\n        import_manager = import_module.get_import_manager(self.api)\n        import_manager.run(\n            path, mirror_name, network_root, autoinstall_file, arch, breed, os_version\n        )\n        return True\n", "path": "cobbler/actions/importer.py"}]}