972 lines
33 KiB
Diff
972 lines
33 KiB
Diff
diff --git a/changelog.d/3842.fixed b/changelog.d/3842.fixed
|
|
new file mode 100644
|
|
index 00000000..6c6d6313
|
|
--- /dev/null
|
|
+++ b/changelog.d/3842.fixed
|
|
@@ -0,0 +1 @@
|
|
+Fix compatibility with Python 3.13
|
|
diff --git a/cobbler/actions/reposync.py b/cobbler/actions/reposync.py
|
|
index c0163350..ec5745fb 100644
|
|
--- a/cobbler/actions/reposync.py
|
|
+++ b/cobbler/actions/reposync.py
|
|
@@ -23,9 +23,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
import logging
|
|
import os
|
|
import os.path
|
|
-import pipes
|
|
-import stat
|
|
+import shlex
|
|
import shutil
|
|
+import stat
|
|
from typing import Optional, Union
|
|
|
|
from cobbler import utils
|
|
@@ -272,9 +272,9 @@ class RepoSync:
|
|
blended = utils.blender(self.api, False, repo)
|
|
flags = blended.get("createrepo_flags", "(ERROR: FLAGS)")
|
|
try:
|
|
- cmd = "createrepo %s %s %s" % (" ".join(mdoptions), flags, pipes.quote(dirname))
|
|
- utils.subprocess_call(cmd)
|
|
- except:
|
|
+ cmd = ["createrepo"] + mdoptions + flags + [shlex.quote(dirname)]
|
|
+ utils.subprocess_call(cmd, shell=False)
|
|
+ except Exception:
|
|
utils.log_exc()
|
|
self.logger.error("createrepo failed.")
|
|
del fnames[:] # we're in the right place
|
|
@@ -302,8 +302,19 @@ class RepoSync:
|
|
dest_path = os.path.join(self.settings.webdir, "repo_mirror", repo.name)
|
|
|
|
# FIXME: wrapper for subprocess that logs to logger
|
|
- cmd = ["wget", "-N", "-np", "-r", "-l", "inf", "-nd", "-P", pipes.quote(dest_path), pipes.quote(repo.mirror)]
|
|
- rc = utils.subprocess_call(cmd)
|
|
+ cmd = [
|
|
+ "wget",
|
|
+ "-N",
|
|
+ "-np",
|
|
+ "-r",
|
|
+ "-l",
|
|
+ "inf",
|
|
+ "-nd",
|
|
+ "-P",
|
|
+ shlex.quote(dest_path),
|
|
+ shlex.quote(repo.mirror),
|
|
+ ]
|
|
+ return_value = utils.subprocess_call(cmd, shell=False)
|
|
|
|
if rc != 0:
|
|
raise CX("cobbler reposync failed")
|
|
@@ -347,9 +358,14 @@ class RepoSync:
|
|
if flags == '':
|
|
flags = self.settings.reposync_rsync_flags
|
|
|
|
- cmd = "rsync %s --delete-after %s --delete --exclude-from=/etc/cobbler/rsync.exclude %s %s" \
|
|
- % (flags, spacer, pipes.quote(repo.mirror), pipes.quote(dest_path))
|
|
- rc = utils.subprocess_call(cmd)
|
|
+ cmd = ["rsync"] + flags + ["--delete-after"]
|
|
+ cmd += spacer + [
|
|
+ "--delete",
|
|
+ "--exclude-from=/etc/cobbler/rsync.exclude",
|
|
+ shlex.quote(repo.mirror),
|
|
+ shlex.quote(dest_path),
|
|
+ ]
|
|
+ return_code = utils.subprocess_call(cmd, shell=False)
|
|
|
|
if rc != 0:
|
|
raise CX("cobbler reposync failed")
|
|
@@ -386,10 +402,11 @@ class RepoSync:
|
|
if not HAS_LIBREPO:
|
|
raise CX("no librepo found, please install python3-librepo")
|
|
|
|
- if os.path.exists("/usr/bin/dnf"):
|
|
- cmd = "/usr/bin/dnf reposync"
|
|
- elif os.path.exists("/usr/bin/reposync"):
|
|
- cmd = "/usr/bin/reposync"
|
|
+ if os.path.exists("/usr/bin/reposync"):
|
|
+ cmd = ["/usr/bin/reposync"]
|
|
+ # DNF5 does not have a reposync subcommand
|
|
+ elif os.path.exists("/usr/bin/dnf"):
|
|
+ cmd = ["/usr/bin/dnf", "reposync"]
|
|
else:
|
|
# Warn about not having yum-utils. We don't want to require it in the package because Fedora 22+ has moved
|
|
# to dnf.
|
|
@@ -451,6 +468,11 @@ class RepoSync:
|
|
# Counter-intuitive, but we want the newish kernels too
|
|
arch = "i686"
|
|
|
|
+ cmd = self.reposync_cmd()
|
|
+ cmd += self.rflags + [
|
|
+ f"--repo={shlex.quote(rest)}",
|
|
+ f"--download-path={shlex.quote(repos_path)}",
|
|
+ ]
|
|
if arch != "none":
|
|
cmd = "%s -a %s" % (cmd, arch)
|
|
|
|
@@ -544,9 +566,11 @@ class RepoSync:
|
|
|
|
if not has_rpm_list:
|
|
# If we have not requested only certain RPMs, use reposync
|
|
- cmd = "%s %s --config=%s --repoid=%s -p %s" \
|
|
- % (cmd, self.rflags, temp_file, pipes.quote(repo.name),
|
|
- pipes.quote(repos_path))
|
|
+ cmd += self.rflags + [
|
|
+ f"--config={temp_file}",
|
|
+ f"--repoid={shlex.quote(repo.name)}",
|
|
+ f"--download-path={shlex.quote(repos_path)}",
|
|
+ ]
|
|
if arch != "none":
|
|
cmd = "%s -a %s" % (cmd, arch)
|
|
|
|
@@ -557,14 +581,14 @@ class RepoSync:
|
|
|
|
use_source = ""
|
|
if arch == "src":
|
|
- use_source = "--source"
|
|
-
|
|
- # Older yumdownloader sometimes explodes on --resolvedeps if this happens to you, upgrade yum & yum-utils
|
|
- extra_flags = self.settings.yumdownloader_flags
|
|
- cmd = "/usr/bin/dnf download"
|
|
- cmd = "%s %s %s --disablerepo=* --enablerepo=%s -c %s --destdir=%s %s" \
|
|
- % (cmd, extra_flags, use_source, pipes.quote(repo.name), temp_file, pipes.quote(dest_path),
|
|
- " ".join(repo.rpm_list))
|
|
+ cmd.append("--source")
|
|
+ cmd += [
|
|
+ "--disablerepo=*",
|
|
+ f"--enablerepo={shlex.quote(repo.name)}",
|
|
+ f"-c={temp_file}",
|
|
+ f"--destdir={shlex.quote(dest_path)}",
|
|
+ ]
|
|
+ cmd += repo.rpm_list
|
|
|
|
# Now regardless of whether we're doing yumdownloader or reposync or whether the repo was http://, ftp://, or
|
|
# rhn://, execute all queued commands here. Any failure at any point stops the operation.
|
|
@@ -669,17 +693,21 @@ class RepoSync:
|
|
dists = ",".join(repo.apt_dists)
|
|
components = ",".join(repo.apt_components)
|
|
|
|
- mirror_data = "--method=%s --host=%s --root=%s --dist=%s --section=%s" \
|
|
- % (pipes.quote(method), pipes.quote(host), pipes.quote(mirror), pipes.quote(dists),
|
|
- pipes.quote(components))
|
|
+ mirror_data = [
|
|
+ f"--method={shlex.quote(method)}",
|
|
+ f"--host={shlex.quote(host)}",
|
|
+ f"--root={shlex.quote(mirror)}",
|
|
+ f"--dist={shlex.quote(dists)}",
|
|
+ f"--section={shlex.quote(components)}",
|
|
+ ]
|
|
|
|
rflags = "--nocleanup"
|
|
for x in repo.yumopts:
|
|
if repo.yumopts[x]:
|
|
rflags += " %s=%s" % (x, repo.yumopts[x])
|
|
else:
|
|
- rflags += " %s" % x
|
|
- cmd = "%s %s %s %s" % (mirror_program, rflags, mirror_data, pipes.quote(dest_path))
|
|
+ rflags.append(repo_yumoption)
|
|
+ cmd = [mirror_program] + rflags + mirror_data + [shlex.quote(dest_path)]
|
|
if repo.arch == RepoArchs.SRC:
|
|
cmd = "%s --source" % cmd
|
|
else:
|
|
diff --git a/tests/actions/reposync_test.py b/tests/actions/reposync_test.py
|
|
index 0bee772c..ee8d1549 100644
|
|
--- a/tests/actions/reposync_test.py
|
|
+++ b/tests/actions/reposync_test.py
|
|
@@ -1,251 +1,592 @@
|
|
+"""
|
|
+Tests that validate the functionality of the module that is responsible for repository synchronization.
|
|
+"""
|
|
+
|
|
import os
|
|
-import glob
|
|
+from pathlib import Path
|
|
+from typing import TYPE_CHECKING, Any, Dict, List, Union
|
|
|
|
import pytest
|
|
|
|
-from cobbler import enums
|
|
+from cobbler import cexceptions, enums
|
|
+from cobbler.actions import reposync
|
|
from cobbler.api import CobblerAPI
|
|
-from cobbler.actions.reposync import RepoSync
|
|
from cobbler.items.repo import Repo
|
|
-from cobbler import cexceptions
|
|
-from tests.conftest import does_not_raise
|
|
|
|
+from tests.conftest import does_not_raise
|
|
|
|
-@pytest.fixture(scope="class")
|
|
-def api():
|
|
- return CobblerAPI()
|
|
+if TYPE_CHECKING:
|
|
+ from pytest_mock import MockerFixture
|
|
|
|
|
|
-@pytest.fixture(scope="class")
|
|
-def reposync(api):
|
|
- test_reposync = RepoSync(api, tries=2, nofail=False)
|
|
+@pytest.fixture(name="reposync_object", scope="function")
|
|
+def fixture_reposync_object(
|
|
+ mocker: "MockerFixture", cobbler_api: CobblerAPI
|
|
+) -> reposync.RepoSync:
|
|
+ settings_mock = mocker.MagicMock()
|
|
+ settings_mock.webdir = "/srv/www/cobbler"
|
|
+ settings_mock.server = "localhost"
|
|
+ settings_mock.http_port = 80
|
|
+ settings_mock.proxy_url_ext = ""
|
|
+ settings_mock.yumdownloader_flags = "--testflag"
|
|
+ settings_mock.reposync_rsync_flags = "--testflag"
|
|
+ settings_mock.reposync_flags = "--testflag"
|
|
+ mocker.patch.object(cobbler_api, "settings", return_value=settings_mock)
|
|
+ test_reposync = reposync.RepoSync(cobbler_api, tries=2, nofail=False)
|
|
return test_reposync
|
|
|
|
|
|
-@pytest.fixture
|
|
-def repo(api):
|
|
+@pytest.fixture(name="repo")
|
|
+def fixture_repo(cobbler_api: CobblerAPI) -> Repo:
|
|
"""
|
|
Creates a Repository "testrepo0" with a keep_updated=True and mirror_locally=True".
|
|
"""
|
|
- test_repo = Repo(api)
|
|
+ test_repo = Repo(cobbler_api)
|
|
test_repo.name = "testrepo0"
|
|
test_repo.mirror_locally = True
|
|
test_repo.keep_updated = True
|
|
- api.add_repo(test_repo)
|
|
return test_repo
|
|
|
|
|
|
@pytest.fixture
|
|
-def remove_repo(api):
|
|
+def remove_repo(cobbler_api: CobblerAPI):
|
|
"""
|
|
Removes the Repository "testrepo0" which can be created with repo.
|
|
"""
|
|
yield
|
|
- test_repo = api.find_repo("testrepo0")
|
|
- if test_repo is not None:
|
|
- api.remove_repo(test_repo.name)
|
|
+ test_repo = cobbler_api.find_repo("testrepo0")
|
|
+ if test_repo is not None and not isinstance(test_repo, list):
|
|
+ cobbler_api.remove_repo(test_repo.name)
|
|
|
|
|
|
-class TestRepoSync:
|
|
- @pytest.mark.usefixtures("remove_repo")
|
|
- @pytest.mark.parametrize(
|
|
- "input_mirror_type,input_mirror,expected_exception",
|
|
- [
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os",
|
|
- does_not_raise()
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.MIRRORLIST,
|
|
- "https://mirrors.fedoraproject.org/mirrorlist?repo=rawhide&arch=x86_64",
|
|
- does_not_raise()
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.METALINK,
|
|
- "https://mirrors.fedoraproject.org/metalink?repo=rawhide&arch=x86_64",
|
|
- does_not_raise()
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://www.example.com/path/to/some/repo",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
+@pytest.fixture(scope="function", autouse=True)
|
|
+def reset_librepo():
|
|
+ has_librepo = reposync.HAS_LIBREPO
|
|
+ yield
|
|
+ reposync.HAS_LIBREPO = has_librepo
|
|
+
|
|
+
|
|
+def test_repo_walker(mocker: "MockerFixture", tmp_path: Path):
|
|
+ # Arrange
|
|
+ def test_fun(arg: Any, top: Any, names: Any):
|
|
+ pass
|
|
+
|
|
+ subdir1 = tmp_path / "sub1"
|
|
+ subdir2 = tmp_path / "sub2"
|
|
+ subdir1.mkdir()
|
|
+ subdir2.mkdir()
|
|
+ spy = mocker.Mock(wraps=test_fun)
|
|
+
|
|
+ # Act
|
|
+ reposync.repo_walker(tmp_path, spy, None) # type: ignore
|
|
+
|
|
+ # Assert
|
|
+ assert spy.mock_calls == [
|
|
+ # settings.yaml is here because of our autouse fixture that we use to restore the settings
|
|
+ mocker.call(None, tmp_path, ["settings.yaml", "sub1", "sub2"]),
|
|
+ mocker.call(None, str(subdir1), []),
|
|
+ mocker.call(None, str(subdir2), []),
|
|
+ ]
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize(
|
|
+ "input_has_librepo,input_path_exists_side_effect,expected_exception,expected_result",
|
|
+ [
|
|
+ (True, [False, True], does_not_raise(), ["/usr/bin/dnf", "reposync"]),
|
|
+ (True, [True, False], does_not_raise(), ["/usr/bin/reposync"]),
|
|
+ (True, [False, False], pytest.raises(cexceptions.CX), ""),
|
|
+ (False, [False, True], pytest.raises(cexceptions.CX), ""),
|
|
+ ],
|
|
+)
|
|
+def test_reposync_cmd(
|
|
+ mocker: "MockerFixture",
|
|
+ reposync_object: reposync.RepoSync,
|
|
+ input_has_librepo: bool,
|
|
+ input_path_exists_side_effect: List[bool],
|
|
+ expected_exception: Any,
|
|
+ expected_result: Union[List[str], str],
|
|
+):
|
|
+ # Arrange
|
|
+ mocker.patch("os.path.exists", side_effect=input_path_exists_side_effect)
|
|
+ reposync.HAS_LIBREPO = input_has_librepo
|
|
+
|
|
+ # Act
|
|
+ with expected_exception:
|
|
+ result = reposync_object.reposync_cmd()
|
|
+
|
|
+ # Assert
|
|
+ assert result == expected_result
|
|
+
|
|
+
|
|
+def test_run(mocker: "MockerFixture", reposync_object: reposync.RepoSync, repo: Repo):
|
|
+ # Arrange
|
|
+ env_vars: Dict[str, Any] = {}
|
|
+ mocker.patch("os.makedirs")
|
|
+ mocker.patch("os.path.isdir", return_value=True)
|
|
+ mocker.patch(
|
|
+ "os.path.join",
|
|
+ side_effect=[
|
|
+ "/srv/www/cobbler/repo_mirror",
|
|
+ "/srv/www/cobbler/repo_mirror/%s" % repo.name,
|
|
],
|
|
)
|
|
- def test_reposync_yum(
|
|
- self,
|
|
- input_mirror_type,
|
|
- input_mirror,
|
|
- expected_exception,
|
|
- api,
|
|
- repo,
|
|
- reposync
|
|
- ):
|
|
- # Arrange
|
|
- test_repo = repo
|
|
- test_repo.breed = enums.RepoBreeds.YUM
|
|
- test_repo.mirror = input_mirror
|
|
- test_repo.mirror_type = input_mirror_type
|
|
- test_repo.rpm_list = "fedora-gpg-keys"
|
|
- test_settings = api.settings()
|
|
- repo_path = os.path.join(test_settings.webdir, "repo_mirror", test_repo.name)
|
|
-
|
|
- # Act & Assert
|
|
- with expected_exception:
|
|
- reposync.run(test_repo.name)
|
|
- result = os.path.exists(repo_path)
|
|
- if test_repo.rpm_list and test_repo.rpm_list != []:
|
|
- for rpm in test_repo.rpm_list:
|
|
- assert glob.glob(os.path.join(repo_path, "**", rpm) + "*.rpm", recursive=True) != []
|
|
- assert result
|
|
- # Test that re-downloading the metadata in .origin/repodata will not result in an error
|
|
- reposync.run(test_repo.name)
|
|
-
|
|
- @pytest.mark.usefixtures("remove_repo")
|
|
- @pytest.mark.parametrize(
|
|
- "input_mirror_type,input_mirror,input_arch,input_rpm_list,expected_exception",
|
|
+ mocker.patch("os.environ", return_value=env_vars)
|
|
+ mocker.patch.object(reposync_object, "repos", return_value=[repo])
|
|
+ mocker.patch.object(reposync_object, "sync")
|
|
+ mocker.patch.object(reposync_object, "update_permissions")
|
|
+ reposync_object.repos = [repo] # type: ignore
|
|
+
|
|
+ # Act
|
|
+ reposync_object.run()
|
|
+
|
|
+ # Assert
|
|
+ # This has to be 0 since all env vars need to be removed after reposync has run.
|
|
+ assert len(env_vars) == 0
|
|
+
|
|
+
|
|
+def test_gen_urlgrab_ssl_opts(reposync_object: reposync.RepoSync):
|
|
+ # Arrange
|
|
+ input_dict: Dict[str, Any] = {}
|
|
+
|
|
+ # Act
|
|
+ result = reposync_object.gen_urlgrab_ssl_opts(input_dict)
|
|
+
|
|
+ # Assert
|
|
+ assert isinstance(result, tuple)
|
|
+ assert len(result) == 2
|
|
+ # The data of the first element is kind of flexible let's skip asserting it for now
|
|
+ assert isinstance(result[1], bool)
|
|
+
|
|
+
|
|
+@pytest.mark.usefixtures("remove_repo")
|
|
+@pytest.mark.parametrize(
|
|
+ "input_mirror_type,input_mirror,expected_exception",
|
|
+ [
|
|
+ (
|
|
+ enums.MirrorType.BASEURL,
|
|
+ "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os",
|
|
+ does_not_raise(),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.MIRRORLIST,
|
|
+ "https://mirrors.fedoraproject.org/mirrorlist?repo=rawhide&arch=x86_64",
|
|
+ does_not_raise(),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.METALINK,
|
|
+ "https://mirrors.fedoraproject.org/metalink?repo=rawhide&arch=x86_64",
|
|
+ does_not_raise(),
|
|
+ ),
|
|
+ ],
|
|
+)
|
|
+def test_reposync_yum(
|
|
+ mocker: "MockerFixture",
|
|
+ input_mirror_type: enums.MirrorType,
|
|
+ input_mirror: str,
|
|
+ expected_exception: Any,
|
|
+ cobbler_api: CobblerAPI,
|
|
+ repo: Repo,
|
|
+ reposync_object: reposync.RepoSync,
|
|
+):
|
|
+ # Arrange
|
|
+ test_repo = repo
|
|
+ test_repo.breed = enums.RepoBreeds.YUM
|
|
+ test_repo.mirror = input_mirror
|
|
+ test_repo.mirror_type = input_mirror_type
|
|
+ test_repo.rpm_list = "fedora-gpg-keys"
|
|
+ test_settings = cobbler_api.settings()
|
|
+ repo_path = os.path.join(test_settings.webdir, "repo_mirror", test_repo.name)
|
|
+ mocked_subprocess = mocker.patch(
|
|
+ "cobbler.utils.subprocess_call", autospec=True, return_value=0
|
|
+ )
|
|
+ mocker.patch.object(
|
|
+ reposync_object, "create_local_file", return_value="/create/local/file"
|
|
+ )
|
|
+ mocker.patch.object(
|
|
+ reposync_object, "reposync_cmd", return_value=["/my/fake/dnf", "reposync"]
|
|
+ )
|
|
+ mocker.patch.object(reposync_object, "rflags", return_value="--fake-r-flakg")
|
|
+ mocker.patch.object(
|
|
+ reposync_object,
|
|
+ "gen_urlgrab_ssl_opts",
|
|
+ return_value=(("TODO", "TODO", "TODO"), False),
|
|
+ )
|
|
+ mocker.patch("os.path.exists", return_value=True)
|
|
+ mocker.patch("shutil.rmtree")
|
|
+ mocker.patch("os.makedirs")
|
|
+ mocked_repo_walker = mocker.patch("cobbler.actions.reposync.repo_walker")
|
|
+ handle_mock = mocker.MagicMock()
|
|
+ result_mock = mocker.MagicMock()
|
|
+ mocker.patch("librepo.Handle", return_value=handle_mock)
|
|
+ mocker.patch("librepo.Result", return_value=result_mock)
|
|
+
|
|
+ # Act & Assert
|
|
+ with expected_exception:
|
|
+ reposync_object.yum_sync(repo)
|
|
+
|
|
+ mocked_subprocess.assert_called_with(
|
|
+ [
|
|
+ "/usr/bin/dnf",
|
|
+ "download",
|
|
+ "--testflag",
|
|
+ "--disablerepo=*",
|
|
+ f"--enablerepo={repo.name}",
|
|
+ "-c=/create/local/file",
|
|
+ f"--destdir={repo_path}",
|
|
+ "fedora-gpg-keys",
|
|
+ ],
|
|
+ shell=False,
|
|
+ )
|
|
+ handle_mock.perform.assert_called_with(result_mock)
|
|
+ assert mocked_repo_walker.call_count == 1
|
|
+
|
|
+
|
|
+@pytest.mark.usefixtures("remove_repo")
|
|
+@pytest.mark.parametrize(
|
|
+ "input_mirror_type,input_mirror,input_arch,input_rpm_list,expected_exception",
|
|
+ [
|
|
+ (
|
|
+ enums.MirrorType.BASEURL,
|
|
+ "http://ftp.debian.org/debian",
|
|
+ enums.RepoArchs.X86_64,
|
|
+ "",
|
|
+ does_not_raise(),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.MIRRORLIST,
|
|
+ "http://ftp.debian.org/debian",
|
|
+ enums.RepoArchs.X86_64,
|
|
+ "",
|
|
+ pytest.raises(cexceptions.CX),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.METALINK,
|
|
+ "http://ftp.debian.org/debian",
|
|
+ enums.RepoArchs.X86_64,
|
|
+ "",
|
|
+ pytest.raises(cexceptions.CX),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.BASEURL,
|
|
+ "http://ftp.debian.org/debian",
|
|
+ enums.RepoArchs.NONE,
|
|
+ "",
|
|
+ pytest.raises(cexceptions.CX),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.BASEURL,
|
|
+ "http://ftp.debian.org/debian",
|
|
+ enums.RepoArchs.X86_64,
|
|
+ "dpkg",
|
|
+ pytest.raises(cexceptions.CX),
|
|
+ ),
|
|
+ ],
|
|
+)
|
|
+def test_reposync_apt(
|
|
+ mocker: "MockerFixture",
|
|
+ input_mirror_type: enums.MirrorType,
|
|
+ input_mirror: str,
|
|
+ input_arch: enums.RepoArchs,
|
|
+ input_rpm_list: str,
|
|
+ expected_exception: Any,
|
|
+ cobbler_api: CobblerAPI,
|
|
+ repo: Repo,
|
|
+ reposync_object: reposync.RepoSync,
|
|
+):
|
|
+ # Arrange
|
|
+ test_repo = repo
|
|
+ test_repo.breed = enums.RepoBreeds.APT
|
|
+ test_repo.arch = input_arch
|
|
+ test_repo.apt_components = "main"
|
|
+ test_repo.apt_dists = "stable"
|
|
+ test_repo.mirror = input_mirror
|
|
+ test_repo.mirror_type = input_mirror_type
|
|
+ test_repo.rpm_list = input_rpm_list
|
|
+ test_settings = cobbler_api.settings()
|
|
+ repo_path = os.path.join(test_settings.webdir, "repo_mirror", test_repo.name)
|
|
+ mocked_subprocess = mocker.patch(
|
|
+ "cobbler.utils.subprocess_call", autospec=True, return_value=0
|
|
+ )
|
|
+ mocker.patch("os.path.exists", return_value=True)
|
|
+
|
|
+ # Act
|
|
+ with expected_exception:
|
|
+ reposync_object.apt_sync(repo)
|
|
+
|
|
+ # Assert
|
|
+ mocked_subprocess.assert_called_with(
|
|
+ [
|
|
+ "/usr/bin/debmirror",
|
|
+ "--nocleanup",
|
|
+ "--method=http",
|
|
+ "--host=ftp.debian.org",
|
|
+ "--root=/debian",
|
|
+ "--dist=stable",
|
|
+ "--section=main",
|
|
+ repo_path,
|
|
+ "--nosource",
|
|
+ "-a=amd64",
|
|
+ ],
|
|
+ shell=False,
|
|
+ )
|
|
+
|
|
+
|
|
+@pytest.mark.usefixtures("remove_repo")
|
|
+@pytest.mark.parametrize(
|
|
+ "input_mirror_type,input_mirror,expected_exception",
|
|
+ [
|
|
+ (
|
|
+ enums.MirrorType.BASEURL,
|
|
+ "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/2",
|
|
+ does_not_raise(),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.MIRRORLIST,
|
|
+ "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/2",
|
|
+ pytest.raises(cexceptions.CX),
|
|
+ ),
|
|
+ (
|
|
+ enums.MirrorType.METALINK,
|
|
+ "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/2",
|
|
+ pytest.raises(cexceptions.CX),
|
|
+ ),
|
|
+ ],
|
|
+)
|
|
+def test_reposync_wget(
|
|
+ mocker: "MockerFixture",
|
|
+ input_mirror_type: enums.MirrorType,
|
|
+ input_mirror: str,
|
|
+ expected_exception: Any,
|
|
+ cobbler_api: CobblerAPI,
|
|
+ repo: Repo,
|
|
+ reposync_object: reposync.RepoSync,
|
|
+):
|
|
+ # Arrange
|
|
+ test_repo = repo
|
|
+ test_repo.breed = enums.RepoBreeds.WGET
|
|
+ test_repo.mirror = input_mirror
|
|
+ test_repo.mirror_type = input_mirror_type
|
|
+ repo_path = os.path.join(
|
|
+ reposync_object.settings.webdir, "repo_mirror", test_repo.name
|
|
+ )
|
|
+ mocked_subprocess = mocker.patch(
|
|
+ "cobbler.utils.subprocess_call", autospec=True, return_value=0
|
|
+ )
|
|
+ mocker.patch("cobbler.actions.reposync.repo_walker")
|
|
+ mocker.patch.object(reposync_object, "create_local_file")
|
|
+
|
|
+ # Act
|
|
+ with expected_exception:
|
|
+ reposync_object.wget_sync(test_repo)
|
|
+
|
|
+ # Assert
|
|
+ mocked_subprocess.assert_called_with(
|
|
+ [
|
|
+ "wget",
|
|
+ "-N",
|
|
+ "-np",
|
|
+ "-r",
|
|
+ "-l",
|
|
+ "inf",
|
|
+ "-nd",
|
|
+ "-P",
|
|
+ repo_path,
|
|
+ input_mirror,
|
|
+ ],
|
|
+ shell=False,
|
|
+ )
|
|
+
|
|
+
|
|
+def test_reposync_rhn(
|
|
+ mocker: "MockerFixture", reposync_object: reposync.RepoSync, repo: Repo
|
|
+):
|
|
+ # Arrange
|
|
+ repo.mirror = "rhn://%s" % repo.name
|
|
+ mocked_subprocess = mocker.patch(
|
|
+ "cobbler.utils.subprocess_call", autospec=True, return_value=0
|
|
+ )
|
|
+ mocker.patch("os.path.isdir", return_value=True)
|
|
+ mocker.patch("os.makedirs")
|
|
+ mocker.patch("cobbler.actions.reposync.repo_walker")
|
|
+ mocker.patch.object(reposync_object, "create_local_file")
|
|
+ mocker.patch.object(
|
|
+ reposync_object, "reposync_cmd", return_value=["/my/fake/reposync"]
|
|
+ )
|
|
+
|
|
+ # Act
|
|
+ reposync_object.rhn_sync(repo)
|
|
+
|
|
+ # Assert
|
|
+ # TODO: Check this more and document how its actually working
|
|
+ mocked_subprocess.assert_called_with(
|
|
[
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://ftp.debian.org/debian",
|
|
- enums.RepoArchs.X86_64,
|
|
- "",
|
|
- does_not_raise()
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.MIRRORLIST,
|
|
- "http://ftp.debian.org/debian",
|
|
- enums.RepoArchs.X86_64,
|
|
- "",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.METALINK,
|
|
- "http://ftp.debian.org/debian",
|
|
- enums.RepoArchs.X86_64,
|
|
- "",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://www.example.com/path/to/some/repo",
|
|
- enums.RepoArchs.X86_64,
|
|
- "",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://ftp.debian.org/debian",
|
|
- enums.RepoArchs.NONE,
|
|
- "",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://ftp.debian.org/debian",
|
|
- enums.RepoArchs.X86_64,
|
|
- "dpkg",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
+ "/my/fake/reposync",
|
|
+ "--testflag",
|
|
+ "--repo=testrepo0",
|
|
+ "--download-path=/srv/www/cobbler/repo_mirror",
|
|
],
|
|
+ shell=False,
|
|
)
|
|
- def test_reposync_apt(
|
|
- self,
|
|
- input_mirror_type,
|
|
- input_mirror,
|
|
- input_arch,
|
|
- input_rpm_list,
|
|
- expected_exception,
|
|
- api,
|
|
- repo,
|
|
- reposync
|
|
- ):
|
|
- # Arrange
|
|
- test_repo = repo
|
|
- test_repo.breed = enums.RepoBreeds.APT
|
|
- test_repo.arch = input_arch
|
|
- test_repo.apt_components = "main"
|
|
- test_repo.apt_dists = "stable"
|
|
- test_repo.mirror = input_mirror
|
|
- test_repo.mirror_type = input_mirror_type
|
|
- test_repo.rpm_list = input_rpm_list
|
|
- test_repo.yumopts = "--exclude=.* --include=dpkg.* --no-check-gpg --rsync-extra=none"
|
|
- test_settings = api.settings()
|
|
- repo_path = os.path.join(test_settings.webdir, "repo_mirror", test_repo.name)
|
|
-
|
|
- # Act & Assert
|
|
- with expected_exception:
|
|
- reposync.run(test_repo.name)
|
|
- result = os.path.exists(repo_path)
|
|
- for rpm in ["dpkg"]:
|
|
- assert glob.glob(os.path.join(repo_path, "**", "dpkg") + "*", recursive=True) != []
|
|
- assert result
|
|
-
|
|
- @pytest.mark.skip("To flaky and thus not reliable. Needs to be mocked to be of use.")
|
|
- @pytest.mark.usefixtures("remove_repo")
|
|
- @pytest.mark.parametrize(
|
|
- "input_mirror_type,input_mirror,expected_exception",
|
|
+
|
|
+
|
|
+def test_reposync_rsync(
|
|
+ mocker: "MockerFixture", reposync_object: reposync.RepoSync, repo: Repo
|
|
+):
|
|
+ # Arrange
|
|
+ mocked_subprocess = mocker.patch("cobbler.utils.subprocess_call", return_value=0)
|
|
+ mocker.patch("cobbler.actions.reposync.repo_walker")
|
|
+ mocker.patch.object(reposync_object, "create_local_file")
|
|
+ repo_path = os.path.join(reposync_object.settings.webdir, "repo_mirror", repo.name)
|
|
+
|
|
+ # Act
|
|
+ reposync_object.rsync_sync(repo)
|
|
+
|
|
+ # Assert
|
|
+ mocked_subprocess.assert_called_with(
|
|
[
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/2",
|
|
- does_not_raise()
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.MIRRORLIST,
|
|
- "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/2",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.METALINK,
|
|
- "http://download.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/2",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
- (
|
|
- enums.MirrorType.BASEURL,
|
|
- "http://www.example.com/path/to/some/repo",
|
|
- pytest.raises(cexceptions.CX)
|
|
- ),
|
|
+ "rsync",
|
|
+ "--testflag",
|
|
+ "--delete-after",
|
|
+ "-e ssh",
|
|
+ "--delete",
|
|
+ "--exclude-from=/etc/cobbler/rsync.exclude",
|
|
+ "/",
|
|
+ repo_path,
|
|
],
|
|
+ shell=False,
|
|
)
|
|
- def test_reposync_wget(
|
|
- self,
|
|
- input_mirror_type,
|
|
- input_mirror,
|
|
- expected_exception,
|
|
- api,
|
|
- repo,
|
|
- reposync
|
|
- ):
|
|
- # Arrange
|
|
- test_repo = repo
|
|
- test_repo.breed = enums.RepoBreeds.WGET
|
|
- test_repo.mirror = input_mirror
|
|
- test_repo.mirror_type = input_mirror_type
|
|
- test_settings = api.settings()
|
|
- repo_path = os.path.join(test_settings.webdir, "repo_mirror", test_repo.name)
|
|
-
|
|
- # Act & Assert
|
|
- with expected_exception:
|
|
- reposync.run(test_repo.name)
|
|
- result = os.path.exists(repo_path)
|
|
- for rpm in ["rpm"]:
|
|
- assert glob.glob(os.path.join(repo_path, "**", "2") + "*", recursive=True) != []
|
|
- assert result
|
|
-
|
|
-
|
|
-@pytest.mark.skip("TODO")
|
|
-def test_reposync_rhn():
|
|
+
|
|
+
|
|
+def test_createrepo_walker(
|
|
+ mocker: "MockerFixture", reposync_object: reposync.RepoSync, repo: Repo
|
|
+):
|
|
# Arrange
|
|
+ input_repo = repo
|
|
+ input_repo.breed = enums.RepoBreeds.RSYNC
|
|
+ input_dirname = ""
|
|
+ input_fnames = []
|
|
+ expected_call = ["createrepo", "--testflags", f"'{input_dirname}'"]
|
|
+ mocked_subprocess = mocker.patch(
|
|
+ "cobbler.utils.subprocess_call", autospec=True, return_value=0
|
|
+ )
|
|
+ mocker.patch(
|
|
+ "cobbler.utils.blender",
|
|
+ autospec=True,
|
|
+ return_value={"createrepo_flags": "--testflags"},
|
|
+ )
|
|
+ mocker.patch("cobbler.utils.remove_yum_olddata")
|
|
+ mocker.patch("cobbler.utils.subprocess_get", return_value="5")
|
|
+ mocker.patch("cobbler.utils.get_family", return_value="TODO")
|
|
+ mocker.patch("os.path.exists", return_value=True)
|
|
+ mocker.patch("os.path.isfile", return_value=True)
|
|
+ mocker.patch.object(reposync_object, "librepo_getinfo", return_value={})
|
|
+
|
|
# Act
|
|
+ reposync_object.createrepo_walker(input_repo, input_dirname, input_fnames)
|
|
+
|
|
# Assert
|
|
- assert False
|
|
+ # TODO: Improve coverage over different cases in method
|
|
+ mocked_subprocess.assert_called_with(expected_call, shell=False)
|
|
|
|
|
|
-@pytest.mark.skip("TODO")
|
|
-def test_reposync_rsync():
|
|
+@pytest.mark.parametrize(
|
|
+ "input_repotype,expected_exception",
|
|
+ [
|
|
+ (enums.RepoBreeds.YUM, does_not_raise()),
|
|
+ (enums.RepoBreeds.RHN, does_not_raise()),
|
|
+ (enums.RepoBreeds.APT, does_not_raise()),
|
|
+ (enums.RepoBreeds.RSYNC, does_not_raise()),
|
|
+ (enums.RepoBreeds.WGET, does_not_raise()),
|
|
+ (enums.RepoBreeds.NONE, pytest.raises(cexceptions.CX)),
|
|
+ ],
|
|
+)
|
|
+def test_sync(
|
|
+ mocker: "MockerFixture",
|
|
+ cobbler_api: CobblerAPI,
|
|
+ reposync_object: reposync.RepoSync,
|
|
+ input_repotype: enums.RepoBreeds,
|
|
+ expected_exception: Any,
|
|
+):
|
|
# Arrange
|
|
+ test_repo = Repo(cobbler_api)
|
|
+ test_repo.breed = input_repotype
|
|
+ rhn_sync_mock = mocker.patch.object(reposync_object, "rhn_sync")
|
|
+ yum_sync_mock = mocker.patch.object(reposync_object, "yum_sync")
|
|
+ apt_sync_mock = mocker.patch.object(reposync_object, "apt_sync")
|
|
+ rsync_sync_mock = mocker.patch.object(reposync_object, "rsync_sync")
|
|
+ wget_sync_mock = mocker.patch.object(reposync_object, "wget_sync")
|
|
+
|
|
# Act
|
|
+ with expected_exception:
|
|
+ reposync_object.sync(test_repo)
|
|
+
|
|
+ # Assert
|
|
+ call_count = sum(
|
|
+ (
|
|
+ rhn_sync_mock.call_count,
|
|
+ yum_sync_mock.call_count,
|
|
+ apt_sync_mock.call_count,
|
|
+ rsync_sync_mock.call_count,
|
|
+ wget_sync_mock.call_count,
|
|
+ )
|
|
+ )
|
|
+ assert call_count == 1
|
|
+
|
|
+
|
|
+def test_librepo_getinfo(
|
|
+ mocker: "MockerFixture", reposync_object: reposync.RepoSync, tmp_path: Path
|
|
+):
|
|
+ # Arrange
|
|
+ handle_mock = mocker.MagicMock()
|
|
+ result_mock = mocker.MagicMock()
|
|
+ mocker.patch("librepo.Handle", return_value=handle_mock)
|
|
+ mocker.patch("librepo.Result", return_value=result_mock)
|
|
+
|
|
+ # Act
|
|
+ reposync_object.librepo_getinfo(str(tmp_path))
|
|
+
|
|
+ # Assert
|
|
+ handle_mock.perform.assert_called_with(result_mock)
|
|
+ result_mock.getinfo.assert_called()
|
|
+
|
|
+
|
|
+def test_create_local_file(
|
|
+ mocker: "MockerFixture", reposync_object: reposync.RepoSync, repo: Repo
|
|
+):
|
|
+ # Arrange
|
|
+ mocker.patch("cobbler.utils.filesystem_helpers.mkdir", autospec=True)
|
|
+ mock_open = mocker.patch("builtins.open", mocker.mock_open())
|
|
+ input_dest_path = ""
|
|
+ input_repo = repo
|
|
+ input_output = True
|
|
+
|
|
+ # Act
|
|
+ reposync_object.create_local_file(input_dest_path, input_repo, output=input_output)
|
|
+
|
|
+ # Assert
|
|
+ # TODO: Extend checks
|
|
+ assert mock_open.call_count == 1
|
|
+ assert mock_open.mock_calls[0] == mocker.call("config.repo", "w", encoding="UTF-8")
|
|
+ mock_open_handle = mock_open()
|
|
+ assert mock_open_handle.write.mock_calls[0] == mocker.call("[testrepo0]\n")
|
|
+ assert mock_open_handle.write.mock_calls[1] == mocker.call("name=testrepo0\n")
|
|
+
|
|
+
|
|
+def test_update_permissions(
|
|
+ mocker: "MockerFixture", reposync_object: reposync.RepoSync
|
|
+):
|
|
+ # Arrange
|
|
+ mocked_subprocess = mocker.patch(
|
|
+ "cobbler.utils.subprocess_call", autospec=True, return_value=0
|
|
+ )
|
|
+ path_to_update = "/my/fake/path"
|
|
+ expected_calls = [
|
|
+ mocker.call(["chown", "-R", "root:www", path_to_update], shell=False),
|
|
+ mocker.call(["chmod", "-R", "755", path_to_update], shell=False),
|
|
+ ]
|
|
+
|
|
+ # Act
|
|
+ reposync_object.update_permissions(path_to_update)
|
|
+
|
|
# Assert
|
|
- assert False
|
|
+ assert mocked_subprocess.mock_calls == expected_calls
|