6b1db1b503
- Align behavior of some modules when using salt-call via symlink (bsc#1215963) - Fix gitfs "__env__" and improve cache cleaning (bsc#1193948) - Remove python-boto dependency for the python3-salt-testsuite package for Tumbleweed - Added: * fix-gitfs-__env__-and-improve-cache-cleaning-bsc-119.patch * dereference-symlinks-to-set-proper-__cli-opt-bsc-121.patch OBS-URL: https://build.opensuse.org/request/show/1125700 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=222
2025 lines
67 KiB
Diff
2025 lines
67 KiB
Diff
From a7c98ce490833ff232946b9715909161b6ba5a46 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
|
|
<psuarezhernandez@suse.com>
|
|
Date: Mon, 13 Nov 2023 11:24:09 +0000
|
|
Subject: [PATCH] Fix gitfs "__env__" and improve cache cleaning
|
|
(bsc#1193948) (#608)
|
|
|
|
* Fix __env__ and improve cache cleaning
|
|
* Move gitfs locks out of cachedir/.git
|
|
|
|
---------
|
|
|
|
Co-authored-by: cmcmarrow <charles.mcmarrow.4@gmail.com>
|
|
---
|
|
changelog/65002.fixed.md | 1 +
|
|
changelog/65086.fixed.md | 1 +
|
|
salt/utils/cache.py | 32 ++
|
|
salt/utils/gitfs.py | 307 +++++++++------
|
|
.../functional/pillar/test_git_pillar.py | 262 +++++++++++++
|
|
tests/pytests/functional/utils/test_cache.py | 83 ++++
|
|
tests/pytests/functional/utils/test_gitfs.py | 275 +++++++++++++
|
|
tests/pytests/functional/utils/test_pillar.py | 365 ++++++++++++++++++
|
|
.../pytests/functional/utils/test_winrepo.py | 164 ++++++++
|
|
tests/pytests/unit/test_minion.py | 31 +-
|
|
tests/pytests/unit/utils/test_gitfs.py | 18 +-
|
|
tests/unit/utils/test_gitfs.py | 21 +-
|
|
12 files changed, 1393 insertions(+), 167 deletions(-)
|
|
create mode 100644 changelog/65002.fixed.md
|
|
create mode 100644 changelog/65086.fixed.md
|
|
create mode 100644 tests/pytests/functional/pillar/test_git_pillar.py
|
|
create mode 100644 tests/pytests/functional/utils/test_cache.py
|
|
create mode 100644 tests/pytests/functional/utils/test_gitfs.py
|
|
create mode 100644 tests/pytests/functional/utils/test_pillar.py
|
|
create mode 100644 tests/pytests/functional/utils/test_winrepo.py
|
|
|
|
diff --git a/changelog/65002.fixed.md b/changelog/65002.fixed.md
|
|
new file mode 100644
|
|
index 0000000000..86ed2d4bcc
|
|
--- /dev/null
|
|
+++ b/changelog/65002.fixed.md
|
|
@@ -0,0 +1 @@
|
|
+Fix __env__ and improve cache cleaning see more info at pull #65017.
|
|
diff --git a/changelog/65086.fixed.md b/changelog/65086.fixed.md
|
|
new file mode 100644
|
|
index 0000000000..292930f0fd
|
|
--- /dev/null
|
|
+++ b/changelog/65086.fixed.md
|
|
@@ -0,0 +1 @@
|
|
+Moved gitfs locks to salt working dir to avoid lock wipes
|
|
diff --git a/salt/utils/cache.py b/salt/utils/cache.py
|
|
index a78a1f70fc..88e7fa2400 100644
|
|
--- a/salt/utils/cache.py
|
|
+++ b/salt/utils/cache.py
|
|
@@ -6,6 +6,7 @@ import functools
|
|
import logging
|
|
import os
|
|
import re
|
|
+import shutil
|
|
import time
|
|
|
|
import salt.config
|
|
@@ -15,6 +16,8 @@ import salt.utils.data
|
|
import salt.utils.dictupdate
|
|
import salt.utils.files
|
|
import salt.utils.msgpack
|
|
+import salt.utils.path
|
|
+import salt.version
|
|
from salt.utils.zeromq import zmq
|
|
|
|
log = logging.getLogger(__name__)
|
|
@@ -345,3 +348,32 @@ def context_cache(func):
|
|
return func(*args, **kwargs)
|
|
|
|
return context_cache_wrap
|
|
+
|
|
+
|
|
+def verify_cache_version(cache_path):
|
|
+ """
|
|
+ Check that the cached version matches the Salt version.
|
|
+ If the cached version does not match the Salt version, wipe the cache.
|
|
+
|
|
+ :return: ``True`` if cache version matches, otherwise ``False``
|
|
+ """
|
|
+ if not os.path.isdir(cache_path):
|
|
+ os.makedirs(cache_path)
|
|
+ with salt.utils.files.fopen(
|
|
+ salt.utils.path.join(cache_path, "cache_version"), "a+"
|
|
+ ) as file:
|
|
+ file.seek(0)
|
|
+ data = "\n".join(file.readlines())
|
|
+ if data != salt.version.__version__:
|
|
+ log.warning(f"Cache version mismatch clearing: {repr(cache_path)}")
|
|
+ file.truncate(0)
|
|
+ file.write(salt.version.__version__)
|
|
+ for item in os.listdir(cache_path):
|
|
+ if item != "cache_version":
|
|
+ item_path = salt.utils.path.join(cache_path, item)
|
|
+ if os.path.isfile(item_path):
|
|
+ os.remove(item_path)
|
|
+ else:
|
|
+ shutil.rmtree(item_path)
|
|
+ return False
|
|
+ return True
|
|
diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py
|
|
index af61aa0dda..061647edac 100644
|
|
--- a/salt/utils/gitfs.py
|
|
+++ b/salt/utils/gitfs.py
|
|
@@ -17,7 +17,6 @@ import os
|
|
import shlex
|
|
import shutil
|
|
import stat
|
|
-import string
|
|
import subprocess
|
|
import time
|
|
import weakref
|
|
@@ -26,6 +25,7 @@ from datetime import datetime
|
|
import salt.ext.tornado.ioloop
|
|
import salt.fileserver
|
|
import salt.syspaths
|
|
+import salt.utils.cache
|
|
import salt.utils.configparser
|
|
import salt.utils.data
|
|
import salt.utils.files
|
|
@@ -253,7 +253,6 @@ class GitProvider:
|
|
val_cb=lambda x, y: str(y),
|
|
)
|
|
self.conf = copy.deepcopy(per_remote_defaults)
|
|
-
|
|
# Remove the 'salt://' from the beginning of any globally-defined
|
|
# per-saltenv mountpoints
|
|
for saltenv, saltenv_conf in self.global_saltenv.items():
|
|
@@ -457,48 +456,38 @@ class GitProvider:
|
|
self.id,
|
|
)
|
|
failhard(self.role)
|
|
-
|
|
- hash_type = getattr(hashlib, self.opts.get("hash_type", "md5"))
|
|
- # Generate full id.
|
|
- # Full id helps decrease the chances of collections in the gitfs cache.
|
|
- try:
|
|
- target = str(self.get_checkout_target())
|
|
- except AttributeError:
|
|
- target = ""
|
|
- self._full_id = "-".join(
|
|
- [
|
|
- getattr(self, "name", ""),
|
|
- self.id,
|
|
- getattr(self, "env", ""),
|
|
- getattr(self, "_root", ""),
|
|
- self.role,
|
|
- getattr(self, "base", ""),
|
|
- getattr(self, "branch", ""),
|
|
- target,
|
|
- ]
|
|
+ if hasattr(self, "name"):
|
|
+ self._cache_basehash = self.name
|
|
+ else:
|
|
+ hash_type = getattr(hashlib, self.opts.get("hash_type", "md5"))
|
|
+ # We loaded this data from yaml configuration files, so, its safe
|
|
+ # to use UTF-8
|
|
+ self._cache_basehash = str(
|
|
+ base64.b64encode(hash_type(self.id.encode("utf-8")).digest()),
|
|
+ encoding="ascii", # base64 only outputs ascii
|
|
+ ).replace(
|
|
+ "/", "_"
|
|
+ ) # replace "/" with "_" to not cause trouble with file system
|
|
+ self._cache_hash = salt.utils.path.join(cache_root, self._cache_basehash)
|
|
+ self._cache_basename = "_"
|
|
+ if self.id.startswith("__env__"):
|
|
+ try:
|
|
+ self._cache_basename = self.get_checkout_target()
|
|
+ except AttributeError:
|
|
+ log.critical(f"__env__ cant generate basename: {self.role} {self.id}")
|
|
+ failhard(self.role)
|
|
+ self._cache_full_basename = salt.utils.path.join(
|
|
+ self._cache_basehash, self._cache_basename
|
|
)
|
|
- # We loaded this data from yaml configuration files, so, its safe
|
|
- # to use UTF-8
|
|
- base64_hash = str(
|
|
- base64.b64encode(hash_type(self._full_id.encode("utf-8")).digest()),
|
|
- encoding="ascii", # base64 only outputs ascii
|
|
- ).replace(
|
|
- "/", "_"
|
|
- ) # replace "/" with "_" to not cause trouble with file system
|
|
-
|
|
- # limit name length to 19, so we don't eat up all the path length for windows
|
|
- # this is due to pygit2 limitations
|
|
- # replace any unknown char with "_" to not cause trouble with file system
|
|
- name_chars = string.ascii_letters + string.digits + "-"
|
|
- cache_name = "".join(
|
|
- c if c in name_chars else "_" for c in getattr(self, "name", "")[:19]
|
|
+ self._cachedir = salt.utils.path.join(self._cache_hash, self._cache_basename)
|
|
+ self._salt_working_dir = salt.utils.path.join(
|
|
+ cache_root, "work", self._cache_full_basename
|
|
)
|
|
-
|
|
- self.cachedir_basename = f"{cache_name}-{base64_hash}"
|
|
- self.cachedir = salt.utils.path.join(cache_root, self.cachedir_basename)
|
|
- self.linkdir = salt.utils.path.join(cache_root, "links", self.cachedir_basename)
|
|
- if not os.path.isdir(self.cachedir):
|
|
- os.makedirs(self.cachedir)
|
|
+ self._linkdir = salt.utils.path.join(
|
|
+ cache_root, "links", self._cache_full_basename
|
|
+ )
|
|
+ if not os.path.isdir(self._cachedir):
|
|
+ os.makedirs(self._cachedir)
|
|
|
|
try:
|
|
self.new = self.init_remote()
|
|
@@ -510,12 +499,32 @@ class GitProvider:
|
|
msg += " Perhaps git is not available."
|
|
log.critical(msg, exc_info=True)
|
|
failhard(self.role)
|
|
+ self.verify_auth()
|
|
+ self.setup_callbacks()
|
|
+ if not os.path.isdir(self._salt_working_dir):
|
|
+ os.makedirs(self._salt_working_dir)
|
|
+ self.fetch_request_check()
|
|
+
|
|
+ def get_cache_basehash(self):
|
|
+ return self._cache_basehash
|
|
+
|
|
+ def get_cache_hash(self):
|
|
+ return self._cache_hash
|
|
|
|
- def full_id(self):
|
|
- return self._full_id
|
|
+ def get_cache_basename(self):
|
|
+ return self._cache_basename
|
|
|
|
- def get_cachedir_basename(self):
|
|
- return self.cachedir_basename
|
|
+ def get_cache_full_basename(self):
|
|
+ return self._cache_full_basename
|
|
+
|
|
+ def get_cachedir(self):
|
|
+ return self._cachedir
|
|
+
|
|
+ def get_linkdir(self):
|
|
+ return self._linkdir
|
|
+
|
|
+ def get_salt_working_dir(self):
|
|
+ return self._salt_working_dir
|
|
|
|
def _get_envs_from_ref_paths(self, refs):
|
|
"""
|
|
@@ -557,7 +566,7 @@ class GitProvider:
|
|
return ret
|
|
|
|
def _get_lock_file(self, lock_type="update"):
|
|
- return salt.utils.path.join(self.gitdir, lock_type + ".lk")
|
|
+ return salt.utils.path.join(self._salt_working_dir, lock_type + ".lk")
|
|
|
|
@classmethod
|
|
def add_conf_overlay(cls, name):
|
|
@@ -644,7 +653,7 @@ class GitProvider:
|
|
# No need to pass an environment to self.root() here since per-saltenv
|
|
# configuration is a gitfs-only feature and check_root() is not used
|
|
# for gitfs.
|
|
- root_dir = salt.utils.path.join(self.cachedir, self.root()).rstrip(os.sep)
|
|
+ root_dir = salt.utils.path.join(self._cachedir, self.root()).rstrip(os.sep)
|
|
if os.path.isdir(root_dir):
|
|
return root_dir
|
|
log.error(
|
|
@@ -816,7 +825,7 @@ class GitProvider:
|
|
desired_refspecs,
|
|
)
|
|
if refspecs != desired_refspecs:
|
|
- conf.set_multivar(remote_section, "fetch", self.refspecs)
|
|
+ conf.set_multivar(remote_section, "fetch", desired_refspecs)
|
|
log.debug(
|
|
"Refspecs for %s remote '%s' set to %s",
|
|
self.role,
|
|
@@ -1069,7 +1078,7 @@ class GitProvider:
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
- def checkout(self):
|
|
+ def checkout(self, fetch_on_fail=True):
|
|
"""
|
|
This function must be overridden in a sub-class
|
|
"""
|
|
@@ -1192,6 +1201,21 @@ class GitProvider:
|
|
else:
|
|
self.url = self.id
|
|
|
|
+ def fetch_request_check(self):
|
|
+ fetch_request = salt.utils.path.join(self._salt_working_dir, "fetch_request")
|
|
+ if os.path.isfile(fetch_request):
|
|
+ log.debug(f"Fetch request: {self._salt_working_dir}")
|
|
+ try:
|
|
+ os.remove(fetch_request)
|
|
+ except OSError as exc:
|
|
+ log.error(
|
|
+ f"Failed to remove Fetch request: {self._salt_working_dir} {exc}",
|
|
+ exc_info=True,
|
|
+ )
|
|
+ self.fetch()
|
|
+ return True
|
|
+ return False
|
|
+
|
|
@property
|
|
def linkdir_walk(self):
|
|
"""
|
|
@@ -1218,14 +1242,14 @@ class GitProvider:
|
|
dirs = []
|
|
self._linkdir_walk.append(
|
|
(
|
|
- salt.utils.path.join(self.linkdir, *parts[: idx + 1]),
|
|
+ salt.utils.path.join(self._linkdir, *parts[: idx + 1]),
|
|
dirs,
|
|
[],
|
|
)
|
|
)
|
|
try:
|
|
# The linkdir itself goes at the beginning
|
|
- self._linkdir_walk.insert(0, (self.linkdir, [parts[0]], []))
|
|
+ self._linkdir_walk.insert(0, (self._linkdir, [parts[0]], []))
|
|
except IndexError:
|
|
pass
|
|
return self._linkdir_walk
|
|
@@ -1275,13 +1299,17 @@ class GitPython(GitProvider):
|
|
role,
|
|
)
|
|
|
|
- def checkout(self):
|
|
+ def checkout(self, fetch_on_fail=True):
|
|
"""
|
|
Checkout the configured branch/tag. We catch an "Exception" class here
|
|
instead of a specific exception class because the exceptions raised by
|
|
GitPython when running these functions vary in different versions of
|
|
GitPython.
|
|
+
|
|
+ fetch_on_fail
|
|
+ If checkout fails perform a fetch then try to checkout again.
|
|
"""
|
|
+ self.fetch_request_check()
|
|
tgt_ref = self.get_checkout_target()
|
|
try:
|
|
head_sha = self.repo.rev_parse("HEAD").hexsha
|
|
@@ -1345,6 +1373,15 @@ class GitPython(GitProvider):
|
|
except Exception: # pylint: disable=broad-except
|
|
continue
|
|
return self.check_root()
|
|
+ if fetch_on_fail:
|
|
+ log.debug(
|
|
+ "Failed to checkout %s from %s remote '%s': fetch and try again",
|
|
+ tgt_ref,
|
|
+ self.role,
|
|
+ self.id,
|
|
+ )
|
|
+ self.fetch()
|
|
+ return self.checkout(fetch_on_fail=False)
|
|
log.error(
|
|
"Failed to checkout %s from %s remote '%s': remote ref does not exist",
|
|
tgt_ref,
|
|
@@ -1360,16 +1397,16 @@ class GitPython(GitProvider):
|
|
initialized by this function.
|
|
"""
|
|
new = False
|
|
- if not os.listdir(self.cachedir):
|
|
+ if not os.listdir(self._cachedir):
|
|
# Repo cachedir is empty, initialize a new repo there
|
|
- self.repo = git.Repo.init(self.cachedir)
|
|
+ self.repo = git.Repo.init(self._cachedir)
|
|
new = True
|
|
else:
|
|
# Repo cachedir exists, try to attach
|
|
try:
|
|
- self.repo = git.Repo(self.cachedir)
|
|
+ self.repo = git.Repo(self._cachedir)
|
|
except git.exc.InvalidGitRepositoryError:
|
|
- log.error(_INVALID_REPO, self.cachedir, self.url, self.role)
|
|
+ log.error(_INVALID_REPO, self._cachedir, self.url, self.role)
|
|
return new
|
|
|
|
self.gitdir = salt.utils.path.join(self.repo.working_dir, ".git")
|
|
@@ -1603,10 +1640,14 @@ class Pygit2(GitProvider):
|
|
except AttributeError:
|
|
return obj.get_object()
|
|
|
|
- def checkout(self):
|
|
+ def checkout(self, fetch_on_fail=True):
|
|
"""
|
|
Checkout the configured branch/tag
|
|
+
|
|
+ fetch_on_fail
|
|
+ If checkout fails perform a fetch then try to checkout again.
|
|
"""
|
|
+ self.fetch_request_check()
|
|
tgt_ref = self.get_checkout_target()
|
|
local_ref = "refs/heads/" + tgt_ref
|
|
remote_ref = "refs/remotes/origin/" + tgt_ref
|
|
@@ -1796,6 +1837,15 @@ class Pygit2(GitProvider):
|
|
exc_info=True,
|
|
)
|
|
return None
|
|
+ if fetch_on_fail:
|
|
+ log.debug(
|
|
+ "Failed to checkout %s from %s remote '%s': fetch and try again",
|
|
+ tgt_ref,
|
|
+ self.role,
|
|
+ self.id,
|
|
+ )
|
|
+ self.fetch()
|
|
+ return self.checkout(fetch_on_fail=False)
|
|
log.error(
|
|
"Failed to checkout %s from %s remote '%s': remote ref does not exist",
|
|
tgt_ref,
|
|
@@ -1837,16 +1887,16 @@ class Pygit2(GitProvider):
|
|
home = os.path.expanduser("~")
|
|
pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = home
|
|
new = False
|
|
- if not os.listdir(self.cachedir):
|
|
+ if not os.listdir(self._cachedir):
|
|
# Repo cachedir is empty, initialize a new repo there
|
|
- self.repo = pygit2.init_repository(self.cachedir)
|
|
+ self.repo = pygit2.init_repository(self._cachedir)
|
|
new = True
|
|
else:
|
|
# Repo cachedir exists, try to attach
|
|
try:
|
|
- self.repo = pygit2.Repository(self.cachedir)
|
|
+ self.repo = pygit2.Repository(self._cachedir)
|
|
except KeyError:
|
|
- log.error(_INVALID_REPO, self.cachedir, self.url, self.role)
|
|
+ log.error(_INVALID_REPO, self._cachedir, self.url, self.role)
|
|
return new
|
|
|
|
self.gitdir = salt.utils.path.join(self.repo.workdir, ".git")
|
|
@@ -2370,6 +2420,7 @@ class GitBase:
|
|
self.file_list_cachedir = salt.utils.path.join(
|
|
self.opts["cachedir"], "file_lists", self.role
|
|
)
|
|
+ salt.utils.cache.verify_cache_version(self.cache_root)
|
|
if init_remotes:
|
|
self.init_remotes(
|
|
remotes if remotes is not None else [],
|
|
@@ -2442,8 +2493,6 @@ class GitBase:
|
|
)
|
|
if hasattr(repo_obj, "repo"):
|
|
# Sanity check and assign the credential parameter
|
|
- repo_obj.verify_auth()
|
|
- repo_obj.setup_callbacks()
|
|
if self.opts["__role"] == "minion" and repo_obj.new:
|
|
# Perform initial fetch on masterless minion
|
|
repo_obj.fetch()
|
|
@@ -2492,7 +2541,7 @@ class GitBase:
|
|
# Don't allow collisions in cachedir naming
|
|
cachedir_map = {}
|
|
for repo in self.remotes:
|
|
- cachedir_map.setdefault(repo.cachedir, []).append(repo.id)
|
|
+ cachedir_map.setdefault(repo.get_cachedir(), []).append(repo.id)
|
|
|
|
collisions = [x for x in cachedir_map if len(cachedir_map[x]) > 1]
|
|
if collisions:
|
|
@@ -2509,48 +2558,42 @@ class GitBase:
|
|
if any(x.new for x in self.remotes):
|
|
self.write_remote_map()
|
|
|
|
+ def _remove_cache_dir(self, cache_dir):
|
|
+ try:
|
|
+ shutil.rmtree(cache_dir)
|
|
+ except OSError as exc:
|
|
+ log.error(
|
|
+ "Unable to remove old %s remote cachedir %s: %s",
|
|
+ self.role,
|
|
+ cache_dir,
|
|
+ exc,
|
|
+ )
|
|
+ return False
|
|
+ log.debug("%s removed old cachedir %s", self.role, cache_dir)
|
|
+ return True
|
|
+
|
|
+ def _iter_remote_hashes(self):
|
|
+ for item in os.listdir(self.cache_root):
|
|
+ if item in ("hash", "refs", "links", "work"):
|
|
+ continue
|
|
+ if os.path.isdir(salt.utils.path.join(self.cache_root, item)):
|
|
+ yield item
|
|
+
|
|
def clear_old_remotes(self):
|
|
"""
|
|
Remove cache directories for remotes no longer configured
|
|
"""
|
|
- try:
|
|
- cachedir_ls = os.listdir(self.cache_root)
|
|
- except OSError:
|
|
- cachedir_ls = []
|
|
- # Remove actively-used remotes from list
|
|
- for repo in self.remotes:
|
|
- try:
|
|
- cachedir_ls.remove(repo.cachedir_basename)
|
|
- except ValueError:
|
|
- pass
|
|
- to_remove = []
|
|
- for item in cachedir_ls:
|
|
- if item in ("hash", "refs"):
|
|
- continue
|
|
- path = salt.utils.path.join(self.cache_root, item)
|
|
- if os.path.isdir(path):
|
|
- to_remove.append(path)
|
|
- failed = []
|
|
- if to_remove:
|
|
- for rdir in to_remove:
|
|
- try:
|
|
- shutil.rmtree(rdir)
|
|
- except OSError as exc:
|
|
- log.error(
|
|
- "Unable to remove old %s remote cachedir %s: %s",
|
|
- self.role,
|
|
- rdir,
|
|
- exc,
|
|
- )
|
|
- failed.append(rdir)
|
|
- else:
|
|
- log.debug("%s removed old cachedir %s", self.role, rdir)
|
|
- for fdir in failed:
|
|
- to_remove.remove(fdir)
|
|
- ret = bool(to_remove)
|
|
- if ret:
|
|
+ change = False
|
|
+ # Remove all hash dirs not part of this group
|
|
+ remote_set = {r.get_cache_basehash() for r in self.remotes}
|
|
+ for item in self._iter_remote_hashes():
|
|
+ if item not in remote_set:
|
|
+ change = self._remove_cache_dir(
|
|
+ salt.utils.path.join(self.cache_root, item) or change
|
|
+ )
|
|
+ if not change:
|
|
self.write_remote_map()
|
|
- return ret
|
|
+ return change
|
|
|
|
def clear_cache(self):
|
|
"""
|
|
@@ -2609,6 +2652,27 @@ class GitBase:
|
|
name = getattr(repo, "name", None)
|
|
if not remotes or (repo.id, name) in remotes or name in remotes:
|
|
try:
|
|
+ # Find and place fetch_request file for all the other branches for this repo
|
|
+ repo_work_hash = os.path.split(repo.get_salt_working_dir())[0]
|
|
+ for branch in os.listdir(repo_work_hash):
|
|
+ # Don't place fetch request in current branch being updated
|
|
+ if branch == repo.get_cache_basename():
|
|
+ continue
|
|
+ branch_salt_dir = salt.utils.path.join(repo_work_hash, branch)
|
|
+ fetch_path = salt.utils.path.join(
|
|
+ branch_salt_dir, "fetch_request"
|
|
+ )
|
|
+ if os.path.isdir(branch_salt_dir):
|
|
+ try:
|
|
+ with salt.utils.files.fopen(fetch_path, "w"):
|
|
+ pass
|
|
+ except OSError as exc: # pylint: disable=broad-except
|
|
+ log.error(
|
|
+ f"Failed to make fetch request: {fetch_path} {exc}",
|
|
+ exc_info=True,
|
|
+ )
|
|
+ else:
|
|
+ log.error(f"Failed to make fetch request: {fetch_path}")
|
|
if repo.fetch():
|
|
# We can't just use the return value from repo.fetch()
|
|
# because the data could still have changed if old
|
|
@@ -2863,7 +2927,7 @@ class GitBase:
|
|
for repo in self.remotes:
|
|
fp_.write(
|
|
salt.utils.stringutils.to_str(
|
|
- "{} = {}\n".format(repo.cachedir_basename, repo.id)
|
|
+ "{} = {}\n".format(repo.get_cache_basehash(), repo.id)
|
|
)
|
|
)
|
|
except OSError:
|
|
@@ -2871,15 +2935,18 @@ class GitBase:
|
|
else:
|
|
log.info("Wrote new %s remote map to %s", self.role, remote_map)
|
|
|
|
- def do_checkout(self, repo):
|
|
+ def do_checkout(self, repo, fetch_on_fail=True):
|
|
"""
|
|
Common code for git_pillar/winrepo to handle locking and checking out
|
|
of a repo.
|
|
+
|
|
+ fetch_on_fail
|
|
+ If checkout fails perform a fetch then try to checkout again.
|
|
"""
|
|
time_start = time.time()
|
|
while time.time() - time_start <= 5:
|
|
try:
|
|
- return repo.checkout()
|
|
+ return repo.checkout(fetch_on_fail=fetch_on_fail)
|
|
except GitLockError as exc:
|
|
if exc.errno == errno.EEXIST:
|
|
time.sleep(0.1)
|
|
@@ -3274,14 +3341,17 @@ class GitPillar(GitBase):
|
|
|
|
role = "git_pillar"
|
|
|
|
- def checkout(self):
|
|
+ def checkout(self, fetch_on_fail=True):
|
|
"""
|
|
Checkout the targeted branches/tags from the git_pillar remotes
|
|
+
|
|
+ fetch_on_fail
|
|
+ If checkout fails perform a fetch then try to checkout again.
|
|
"""
|
|
self.pillar_dirs = OrderedDict()
|
|
self.pillar_linked_dirs = []
|
|
for repo in self.remotes:
|
|
- cachedir = self.do_checkout(repo)
|
|
+ cachedir = self.do_checkout(repo, fetch_on_fail=fetch_on_fail)
|
|
if cachedir is not None:
|
|
# Figure out which environment this remote should be assigned
|
|
if repo.branch == "__env__" and hasattr(repo, "all_saltenvs"):
|
|
@@ -3298,8 +3368,8 @@ class GitPillar(GitBase):
|
|
env = "base" if tgt == repo.base else tgt
|
|
if repo._mountpoint:
|
|
if self.link_mountpoint(repo):
|
|
- self.pillar_dirs[repo.linkdir] = env
|
|
- self.pillar_linked_dirs.append(repo.linkdir)
|
|
+ self.pillar_dirs[repo.get_linkdir()] = env
|
|
+ self.pillar_linked_dirs.append(repo.get_linkdir())
|
|
else:
|
|
self.pillar_dirs[cachedir] = env
|
|
|
|
@@ -3308,17 +3378,19 @@ class GitPillar(GitBase):
|
|
Ensure that the mountpoint is present in the correct location and
|
|
points at the correct path
|
|
"""
|
|
- lcachelink = salt.utils.path.join(repo.linkdir, repo._mountpoint)
|
|
- lcachedest = salt.utils.path.join(repo.cachedir, repo.root()).rstrip(os.sep)
|
|
+ lcachelink = salt.utils.path.join(repo.get_linkdir(), repo._mountpoint)
|
|
+ lcachedest = salt.utils.path.join(repo.get_cachedir(), repo.root()).rstrip(
|
|
+ os.sep
|
|
+ )
|
|
wipe_linkdir = False
|
|
create_link = False
|
|
try:
|
|
with repo.gen_lock(lock_type="mountpoint", timeout=10):
|
|
- walk_results = list(os.walk(repo.linkdir, followlinks=False))
|
|
+ walk_results = list(os.walk(repo.get_linkdir(), followlinks=False))
|
|
if walk_results != repo.linkdir_walk:
|
|
log.debug(
|
|
"Results of walking %s differ from expected results",
|
|
- repo.linkdir,
|
|
+ repo.get_linkdir(),
|
|
)
|
|
log.debug("Walk results: %s", walk_results)
|
|
log.debug("Expected results: %s", repo.linkdir_walk)
|
|
@@ -3379,7 +3451,7 @@ class GitPillar(GitBase):
|
|
# Wiping implies that we need to create the link
|
|
create_link = True
|
|
try:
|
|
- shutil.rmtree(repo.linkdir)
|
|
+ shutil.rmtree(repo.get_linkdir())
|
|
except OSError:
|
|
pass
|
|
try:
|
|
@@ -3431,6 +3503,9 @@ class GitPillar(GitBase):
|
|
class WinRepo(GitBase):
|
|
"""
|
|
Functionality specific to the winrepo runner
|
|
+
|
|
+ fetch_on_fail
|
|
+ If checkout fails perform a fetch then try to checkout again.
|
|
"""
|
|
|
|
role = "winrepo"
|
|
@@ -3438,12 +3513,12 @@ class WinRepo(GitBase):
|
|
# out the repos.
|
|
winrepo_dirs = {}
|
|
|
|
- def checkout(self):
|
|
+ def checkout(self, fetch_on_fail=True):
|
|
"""
|
|
Checkout the targeted branches/tags from the winrepo remotes
|
|
"""
|
|
self.winrepo_dirs = {}
|
|
for repo in self.remotes:
|
|
- cachedir = self.do_checkout(repo)
|
|
+ cachedir = self.do_checkout(repo, fetch_on_fail=fetch_on_fail)
|
|
if cachedir is not None:
|
|
self.winrepo_dirs[repo.id] = cachedir
|
|
diff --git a/tests/pytests/functional/pillar/test_git_pillar.py b/tests/pytests/functional/pillar/test_git_pillar.py
|
|
new file mode 100644
|
|
index 0000000000..6fd3dee431
|
|
--- /dev/null
|
|
+++ b/tests/pytests/functional/pillar/test_git_pillar.py
|
|
@@ -0,0 +1,262 @@
|
|
+import pytest
|
|
+
|
|
+from salt.pillar.git_pillar import ext_pillar
|
|
+from salt.utils.immutabletypes import ImmutableDict, ImmutableList
|
|
+from tests.support.mock import patch
|
|
+
|
|
+pytestmark = [
|
|
+ pytest.mark.slow_test,
|
|
+]
|
|
+
|
|
+
|
|
+try:
|
|
+ import git # pylint: disable=unused-import
|
|
+
|
|
+ HAS_GITPYTHON = True
|
|
+except ImportError:
|
|
+ HAS_GITPYTHON = False
|
|
+
|
|
+
|
|
+try:
|
|
+ import pygit2 # pylint: disable=unused-import
|
|
+
|
|
+ HAS_PYGIT2 = True
|
|
+except ImportError:
|
|
+ HAS_PYGIT2 = False
|
|
+
|
|
+
|
|
+skipif_no_gitpython = pytest.mark.skipif(not HAS_GITPYTHON, reason="Missing gitpython")
|
|
+skipif_no_pygit2 = pytest.mark.skipif(not HAS_PYGIT2, reason="Missing pygit2")
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def git_pillar_opts(salt_master, tmp_path):
|
|
+ opts = dict(salt_master.config)
|
|
+ opts["cachedir"] = str(tmp_path)
|
|
+ for key, item in opts.items():
|
|
+ if isinstance(item, ImmutableDict):
|
|
+ opts[key] = dict(item)
|
|
+ elif isinstance(item, ImmutableList):
|
|
+ opts[key] = list(item)
|
|
+ return opts
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def gitpython_pillar_opts(git_pillar_opts):
|
|
+ git_pillar_opts["verified_git_pillar_provider"] = "gitpython"
|
|
+ return git_pillar_opts
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def pygit2_pillar_opts(git_pillar_opts):
|
|
+ git_pillar_opts["verified_git_pillar_provider"] = "pygit2"
|
|
+ return git_pillar_opts
|
|
+
|
|
+
|
|
+def _get_ext_pillar(minion, pillar_opts, grains, *repos):
|
|
+ with patch("salt.pillar.git_pillar.__opts__", pillar_opts, create=True):
|
|
+ with patch("salt.pillar.git_pillar.__grains__", grains, create=True):
|
|
+ return ext_pillar(minion, None, *repos)
|
|
+
|
|
+
|
|
+def _test_simple(pillar_opts, grains):
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ assert data == {"key": "value"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_simple(gitpython_pillar_opts, grains):
|
|
+ _test_simple(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_simple(pygit2_pillar_opts, grains):
|
|
+ _test_simple(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_missing_env(pillar_opts, grains):
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ {
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git": [
|
|
+ {"env": "misssing"}
|
|
+ ]
|
|
+ },
|
|
+ )
|
|
+ assert data == {}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_missing_env(gitpython_pillar_opts, grains):
|
|
+ _test_missing_env(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_missing_env(pygit2_pillar_opts, grains):
|
|
+ _test_missing_env(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_env(pillar_opts, grains):
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ {
|
|
+ "other https://github.com/saltstack/salt-test-pillar-gitfs-2.git": [
|
|
+ {"env": "other_env"}
|
|
+ ]
|
|
+ },
|
|
+ )
|
|
+ assert data == {"other": "env"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_env(gitpython_pillar_opts, grains):
|
|
+ _test_env(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_env(pygit2_pillar_opts, grains):
|
|
+ _test_env(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_branch(pillar_opts, grains):
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ "branch https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ assert data == {"key": "data"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_branch(gitpython_pillar_opts, grains):
|
|
+ _test_branch(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_branch(pygit2_pillar_opts, grains):
|
|
+ _test_branch(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_simple_dynamic(pillar_opts, grains):
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ assert data == {"key": "value"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_simple_dynamic(gitpython_pillar_opts, grains):
|
|
+ _test_simple_dynamic(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_simple_dynamic(pygit2_pillar_opts, grains):
|
|
+ _test_simple_dynamic(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_missing_env_dynamic(pillar_opts, grains):
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ {
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git": [
|
|
+ {"env": "misssing"}
|
|
+ ]
|
|
+ },
|
|
+ )
|
|
+ assert data == {}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_missing_env_dynamic(gitpython_pillar_opts, grains):
|
|
+ _test_missing_env_dynamic(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_missing_env_dynamic(pygit2_pillar_opts, grains):
|
|
+ _test_missing_env_dynamic(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_pillarenv_dynamic(pillar_opts, grains):
|
|
+ pillar_opts["pillarenv"] = "branch"
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ assert data == {"key": "data"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_pillarenv_dynamic(gitpython_pillar_opts, grains):
|
|
+ _test_pillarenv_dynamic(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_pillarenv_dynamic(pygit2_pillar_opts, grains):
|
|
+ _test_pillarenv_dynamic(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_multiple(pillar_opts, grains):
|
|
+ pillar_opts["pillarenv"] = "branch"
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ )
|
|
+ assert data == {"key": "data"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_multiple(gitpython_pillar_opts, grains):
|
|
+ _test_multiple(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_multiple(pygit2_pillar_opts, grains):
|
|
+ _test_multiple(pygit2_pillar_opts, grains)
|
|
+
|
|
+
|
|
+def _test_multiple_2(pillar_opts, grains):
|
|
+ data = _get_ext_pillar(
|
|
+ "minion",
|
|
+ pillar_opts,
|
|
+ grains,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ )
|
|
+ assert data == {
|
|
+ "key": "value",
|
|
+ "key1": "value1",
|
|
+ "key2": "value2",
|
|
+ "key4": "value4",
|
|
+ "data1": "d",
|
|
+ "data2": "d2",
|
|
+ }
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_multiple_2(gitpython_pillar_opts, grains):
|
|
+ _test_multiple_2(gitpython_pillar_opts, grains)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_multiple_2(pygit2_pillar_opts, grains):
|
|
+ _test_multiple_2(pygit2_pillar_opts, grains)
|
|
diff --git a/tests/pytests/functional/utils/test_cache.py b/tests/pytests/functional/utils/test_cache.py
|
|
new file mode 100644
|
|
index 0000000000..d405b8246f
|
|
--- /dev/null
|
|
+++ b/tests/pytests/functional/utils/test_cache.py
|
|
@@ -0,0 +1,83 @@
|
|
+import os
|
|
+
|
|
+import pytest
|
|
+
|
|
+import salt.utils.cache
|
|
+import salt.utils.files
|
|
+import salt.utils.path
|
|
+import salt.version
|
|
+
|
|
+_DUMMY_FILES = (
|
|
+ "data.txt",
|
|
+ "foo.t2",
|
|
+ "bar.t3",
|
|
+ "nested/test",
|
|
+ "nested/cache.txt",
|
|
+ "n/n1/n2/n3/n4/n5",
|
|
+)
|
|
+
|
|
+
|
|
+def _make_dummy_files(tmp_path):
|
|
+ for full_path in _DUMMY_FILES:
|
|
+ full_path = salt.utils.path.join(tmp_path, full_path)
|
|
+ path, _ = os.path.split(full_path)
|
|
+ if not os.path.isdir(path):
|
|
+ os.makedirs(path)
|
|
+ with salt.utils.files.fopen(full_path, "w") as file:
|
|
+ file.write("data")
|
|
+
|
|
+
|
|
+def _dummy_files_exists(tmp_path):
|
|
+ """
|
|
+ True if all files exists
|
|
+ False if all files are missing
|
|
+ None if some files exists and others are missing
|
|
+ """
|
|
+ ret = None
|
|
+ for full_path in _DUMMY_FILES:
|
|
+ full_path = salt.utils.path.join(tmp_path, full_path)
|
|
+ is_file = os.path.isfile(full_path)
|
|
+ if ret is None:
|
|
+ ret = is_file
|
|
+ elif ret is not is_file:
|
|
+ return None # Some files are found and others are missing
|
|
+ return ret
|
|
+
|
|
+
|
|
+def test_verify_cache_version_bad_path():
|
|
+ with pytest.raises(ValueError):
|
|
+ # cache version should fail if given bad file python
|
|
+ salt.utils.cache.verify_cache_version("\0/bad/path")
|
|
+
|
|
+
|
|
+def test_verify_cache_version(tmp_path):
|
|
+ # cache version should make dir if it does not exist
|
|
+ tmp_path = str(salt.utils.path.join(str(tmp_path), "work", "salt"))
|
|
+ cache_version = salt.utils.path.join(tmp_path, "cache_version")
|
|
+
|
|
+ # check that cache clears when no cache_version is present
|
|
+ _make_dummy_files(tmp_path)
|
|
+ assert salt.utils.cache.verify_cache_version(tmp_path) is False
|
|
+ assert _dummy_files_exists(tmp_path) is False
|
|
+
|
|
+ # check that cache_version has correct salt version
|
|
+ with salt.utils.files.fopen(cache_version, "r") as file:
|
|
+ assert "\n".join(file.readlines()) == salt.version.__version__
|
|
+
|
|
+ # check that cache does not get clear when check is called multiple times
|
|
+ _make_dummy_files(tmp_path)
|
|
+ for _ in range(3):
|
|
+ assert salt.utils.cache.verify_cache_version(tmp_path) is True
|
|
+ assert _dummy_files_exists(tmp_path) is True
|
|
+
|
|
+ # check that cache clears when a different version is present
|
|
+ with salt.utils.files.fopen(cache_version, "w") as file:
|
|
+ file.write("-1")
|
|
+ assert salt.utils.cache.verify_cache_version(tmp_path) is False
|
|
+ assert _dummy_files_exists(tmp_path) is False
|
|
+
|
|
+ # check that cache does not get clear when check is called multiple times
|
|
+ _make_dummy_files(tmp_path)
|
|
+ for _ in range(3):
|
|
+ assert salt.utils.cache.verify_cache_version(tmp_path) is True
|
|
+ assert _dummy_files_exists(tmp_path) is True
|
|
diff --git a/tests/pytests/functional/utils/test_gitfs.py b/tests/pytests/functional/utils/test_gitfs.py
|
|
new file mode 100644
|
|
index 0000000000..30a5f147fa
|
|
--- /dev/null
|
|
+++ b/tests/pytests/functional/utils/test_gitfs.py
|
|
@@ -0,0 +1,275 @@
|
|
+import os.path
|
|
+
|
|
+import pytest
|
|
+
|
|
+from salt.fileserver.gitfs import PER_REMOTE_ONLY, PER_REMOTE_OVERRIDES
|
|
+from salt.utils.gitfs import GitFS, GitPython, Pygit2
|
|
+from salt.utils.immutabletypes import ImmutableDict, ImmutableList
|
|
+
|
|
+pytestmark = [
|
|
+ pytest.mark.slow_test,
|
|
+]
|
|
+
|
|
+
|
|
+try:
|
|
+ import git # pylint: disable=unused-import
|
|
+
|
|
+ HAS_GITPYTHON = True
|
|
+except ImportError:
|
|
+ HAS_GITPYTHON = False
|
|
+
|
|
+
|
|
+try:
|
|
+ import pygit2 # pylint: disable=unused-import
|
|
+
|
|
+ HAS_PYGIT2 = True
|
|
+except ImportError:
|
|
+ HAS_PYGIT2 = False
|
|
+
|
|
+
|
|
+skipif_no_gitpython = pytest.mark.skipif(not HAS_GITPYTHON, reason="Missing gitpython")
|
|
+skipif_no_pygit2 = pytest.mark.skipif(not HAS_PYGIT2, reason="Missing pygit2")
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def gitfs_opts(salt_factories, tmp_path):
|
|
+ config_defaults = {"cachedir": str(tmp_path)}
|
|
+ factory = salt_factories.salt_master_daemon(
|
|
+ "gitfs-functional-master", defaults=config_defaults
|
|
+ )
|
|
+ config_defaults = dict(factory.config)
|
|
+ for key, item in config_defaults.items():
|
|
+ if isinstance(item, ImmutableDict):
|
|
+ config_defaults[key] = dict(item)
|
|
+ elif isinstance(item, ImmutableList):
|
|
+ config_defaults[key] = list(item)
|
|
+ return config_defaults
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def gitpython_gitfs_opts(gitfs_opts):
|
|
+ gitfs_opts["verified_gitfs_provider"] = "gitpython"
|
|
+ GitFS.instance_map.clear() # wipe instance_map object map for clean run
|
|
+ return gitfs_opts
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def pygit2_gitfs_opts(gitfs_opts):
|
|
+ gitfs_opts["verified_gitfs_provider"] = "pygit2"
|
|
+ GitFS.instance_map.clear() # wipe instance_map object map for clean run
|
|
+ return gitfs_opts
|
|
+
|
|
+
|
|
+def _get_gitfs(opts, *remotes):
|
|
+ return GitFS(
|
|
+ opts,
|
|
+ remotes,
|
|
+ per_remote_overrides=PER_REMOTE_OVERRIDES,
|
|
+ per_remote_only=PER_REMOTE_ONLY,
|
|
+ )
|
|
+
|
|
+
|
|
+def _test_gitfs_simple(gitfs_opts):
|
|
+ g = _get_gitfs(
|
|
+ gitfs_opts,
|
|
+ {"https://github.com/saltstack/salt-test-pillar-gitfs.git": [{"name": "bob"}]},
|
|
+ )
|
|
+ g.fetch_remotes()
|
|
+ assert len(g.remotes) == 1
|
|
+ assert set(g.file_list({"saltenv": "main"})) == {".gitignore", "README.md"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_gitfs_simple(gitpython_gitfs_opts):
|
|
+ _test_gitfs_simple(gitpython_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_gitfs_simple(pygit2_gitfs_opts):
|
|
+ _test_gitfs_simple(pygit2_gitfs_opts)
|
|
+
|
|
+
|
|
+def _test_gitfs_simple_base(gitfs_opts):
|
|
+ g = _get_gitfs(
|
|
+ gitfs_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ g.fetch_remotes()
|
|
+ assert len(g.remotes) == 1
|
|
+ assert set(g.file_list({"saltenv": "base"})) == {
|
|
+ ".gitignore",
|
|
+ "README.md",
|
|
+ "file.sls",
|
|
+ "top.sls",
|
|
+ }
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_gitfs_simple_base(gitpython_gitfs_opts):
|
|
+ _test_gitfs_simple_base(gitpython_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_gitfs_simple_base(pygit2_gitfs_opts):
|
|
+ _test_gitfs_simple_base(pygit2_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_gitfs_provider(gitpython_gitfs_opts):
|
|
+ g = _get_gitfs(
|
|
+ gitpython_gitfs_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ assert len(g.remotes) == 1
|
|
+ assert g.provider == "gitpython"
|
|
+ assert isinstance(g.remotes[0], GitPython)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_gitfs_provider(pygit2_gitfs_opts):
|
|
+ g = _get_gitfs(
|
|
+ pygit2_gitfs_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ assert len(g.remotes) == 1
|
|
+ assert g.provider == "pygit2"
|
|
+ assert isinstance(g.remotes[0], Pygit2)
|
|
+
|
|
+
|
|
+def _test_gitfs_minion(gitfs_opts):
|
|
+ gitfs_opts["__role"] = "minion"
|
|
+ g = _get_gitfs(
|
|
+ gitfs_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ g.fetch_remotes()
|
|
+ assert len(g.remotes) == 1
|
|
+ assert set(g.file_list({"saltenv": "base"})) == {
|
|
+ ".gitignore",
|
|
+ "README.md",
|
|
+ "file.sls",
|
|
+ "top.sls",
|
|
+ }
|
|
+ assert set(g.file_list({"saltenv": "main"})) == {".gitignore", "README.md"}
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_gitfs_minion(gitpython_gitfs_opts):
|
|
+ _test_gitfs_minion(gitpython_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_gitfs_minion(pygit2_gitfs_opts):
|
|
+ _test_gitfs_minion(pygit2_gitfs_opts)
|
|
+
|
|
+
|
|
+def _test_fetch_request_with_mountpoint(opts):
|
|
+ mpoint = [{"mountpoint": "salt/m"}]
|
|
+ p = _get_gitfs(
|
|
+ opts,
|
|
+ {"https://github.com/saltstack/salt-test-pillar-gitfs.git": mpoint},
|
|
+ )
|
|
+ p.fetch_remotes()
|
|
+ assert len(p.remotes) == 1
|
|
+ repo = p.remotes[0]
|
|
+ assert repo.mountpoint("testmount") == "salt/m"
|
|
+ assert set(p.file_list({"saltenv": "testmount"})) == {
|
|
+ "salt/m/test_dir1/testfile3",
|
|
+ "salt/m/test_dir1/test_dir2/testfile2",
|
|
+ "salt/m/.gitignore",
|
|
+ "salt/m/README.md",
|
|
+ "salt/m/test_dir1/test_dir2/testfile1",
|
|
+ }
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_fetch_request_with_mountpoint(gitpython_gitfs_opts):
|
|
+ _test_fetch_request_with_mountpoint(gitpython_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_fetch_request_with_mountpoint(pygit2_gitfs_opts):
|
|
+ _test_fetch_request_with_mountpoint(pygit2_gitfs_opts)
|
|
+
|
|
+
|
|
+def _test_name(opts):
|
|
+ p = _get_gitfs(
|
|
+ opts,
|
|
+ {
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git": [
|
|
+ {"name": "name1"}
|
|
+ ]
|
|
+ },
|
|
+ {
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git": [
|
|
+ {"name": "name2"}
|
|
+ ]
|
|
+ },
|
|
+ )
|
|
+ p.fetch_remotes()
|
|
+ assert len(p.remotes) == 2
|
|
+ repo = p.remotes[0]
|
|
+ repo2 = p.remotes[1]
|
|
+ assert repo.get_cache_basehash() == "name1"
|
|
+ assert repo2.get_cache_basehash() == "name2"
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_name(gitpython_gitfs_opts):
|
|
+ _test_name(gitpython_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_name(pygit2_gitfs_opts):
|
|
+ _test_name(pygit2_gitfs_opts)
|
|
+
|
|
+
|
|
+def _test_remote_map(opts):
|
|
+ p = _get_gitfs(
|
|
+ opts,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ p.fetch_remotes()
|
|
+ assert len(p.remotes) == 1
|
|
+ assert os.path.isfile(os.path.join(opts["cachedir"], "gitfs", "remote_map.txt"))
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_remote_map(gitpython_gitfs_opts):
|
|
+ _test_remote_map(gitpython_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_remote_map(pygit2_gitfs_opts):
|
|
+ _test_remote_map(pygit2_gitfs_opts)
|
|
+
|
|
+
|
|
+def _test_lock(opts):
|
|
+ g = _get_gitfs(
|
|
+ opts,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ g.fetch_remotes()
|
|
+ assert len(g.remotes) == 1
|
|
+ repo = g.remotes[0]
|
|
+ assert repo.get_salt_working_dir() in repo._get_lock_file()
|
|
+ assert repo.lock() == (
|
|
+ [
|
|
+ "Set update lock for gitfs remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'"
|
|
+ ],
|
|
+ [],
|
|
+ )
|
|
+ assert os.path.isfile(repo._get_lock_file())
|
|
+ assert repo.clear_lock() == (
|
|
+ [
|
|
+ "Removed update lock for gitfs remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'"
|
|
+ ],
|
|
+ [],
|
|
+ )
|
|
+ assert not os.path.isfile(repo._get_lock_file())
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_lock(gitpython_gitfs_opts):
|
|
+ _test_lock(gitpython_gitfs_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_lock(pygit2_gitfs_opts):
|
|
+ _test_lock(pygit2_gitfs_opts)
|
|
diff --git a/tests/pytests/functional/utils/test_pillar.py b/tests/pytests/functional/utils/test_pillar.py
|
|
new file mode 100644
|
|
index 0000000000..143edbf6ff
|
|
--- /dev/null
|
|
+++ b/tests/pytests/functional/utils/test_pillar.py
|
|
@@ -0,0 +1,365 @@
|
|
+import os
|
|
+
|
|
+import pytest
|
|
+
|
|
+from salt.pillar.git_pillar import GLOBAL_ONLY, PER_REMOTE_ONLY, PER_REMOTE_OVERRIDES
|
|
+from salt.utils.gitfs import GitPillar, GitPython, Pygit2
|
|
+from salt.utils.immutabletypes import ImmutableDict, ImmutableList
|
|
+
|
|
+pytestmark = [
|
|
+ pytest.mark.slow_test,
|
|
+]
|
|
+
|
|
+
|
|
+try:
|
|
+ import git # pylint: disable=unused-import
|
|
+
|
|
+ HAS_GITPYTHON = True
|
|
+except ImportError:
|
|
+ HAS_GITPYTHON = False
|
|
+
|
|
+
|
|
+try:
|
|
+ import pygit2 # pylint: disable=unused-import
|
|
+
|
|
+ HAS_PYGIT2 = True
|
|
+except ImportError:
|
|
+ HAS_PYGIT2 = False
|
|
+
|
|
+
|
|
+skipif_no_gitpython = pytest.mark.skipif(not HAS_GITPYTHON, reason="Missing gitpython")
|
|
+skipif_no_pygit2 = pytest.mark.skipif(not HAS_PYGIT2, reason="Missing pygit2")
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def pillar_opts(salt_factories, tmp_path):
|
|
+ config_defaults = {"cachedir": str(tmp_path)}
|
|
+ factory = salt_factories.salt_master_daemon(
|
|
+ "pillar-functional-master", defaults=config_defaults
|
|
+ )
|
|
+ config_defaults = dict(factory.config)
|
|
+ for key, item in config_defaults.items():
|
|
+ if isinstance(item, ImmutableDict):
|
|
+ config_defaults[key] = dict(item)
|
|
+ elif isinstance(item, ImmutableList):
|
|
+ config_defaults[key] = list(item)
|
|
+ return config_defaults
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def gitpython_pillar_opts(pillar_opts):
|
|
+ pillar_opts["verified_git_pillar_provider"] = "gitpython"
|
|
+ return pillar_opts
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def pygit2_pillar_opts(pillar_opts):
|
|
+ pillar_opts["verified_git_pillar_provider"] = "pygit2"
|
|
+ return pillar_opts
|
|
+
|
|
+
|
|
+def _get_pillar(opts, *remotes):
|
|
+ return GitPillar(
|
|
+ opts,
|
|
+ remotes,
|
|
+ per_remote_overrides=PER_REMOTE_OVERRIDES,
|
|
+ per_remote_only=PER_REMOTE_ONLY,
|
|
+ global_only=GLOBAL_ONLY,
|
|
+ )
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_pillar_provider(gitpython_pillar_opts):
|
|
+ p = _get_pillar(
|
|
+ gitpython_pillar_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ assert len(p.remotes) == 1
|
|
+ assert p.provider == "gitpython"
|
|
+ assert isinstance(p.remotes[0], GitPython)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_pillar_provider(pygit2_pillar_opts):
|
|
+ p = _get_pillar(
|
|
+ pygit2_pillar_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ assert len(p.remotes) == 1
|
|
+ assert p.provider == "pygit2"
|
|
+ assert isinstance(p.remotes[0], Pygit2)
|
|
+
|
|
+
|
|
+def _test_env(opts):
|
|
+ p = _get_pillar(
|
|
+ opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ assert len(p.remotes) == 1
|
|
+ p.checkout()
|
|
+ repo = p.remotes[0]
|
|
+ # test that two different pillarenvs can exist at the same time
|
|
+ files = set(os.listdir(repo.get_cachedir()))
|
|
+ for f in (".gitignore", "README.md", "file.sls", "top.sls"):
|
|
+ assert f in files
|
|
+ opts["pillarenv"] = "main"
|
|
+ p2 = _get_pillar(
|
|
+ opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ assert len(p.remotes) == 1
|
|
+ p2.checkout()
|
|
+ repo2 = p2.remotes[0]
|
|
+ files = set(os.listdir(repo2.get_cachedir()))
|
|
+ for f in (".gitignore", "README.md"):
|
|
+ assert f in files
|
|
+ for f in ("file.sls", "top.sls", "back.sls", "rooms.sls"):
|
|
+ assert f not in files
|
|
+ assert repo.get_cachedir() != repo2.get_cachedir()
|
|
+ files = set(os.listdir(repo.get_cachedir()))
|
|
+ for f in (".gitignore", "README.md", "file.sls", "top.sls"):
|
|
+ assert f in files
|
|
+
|
|
+ # double check cache paths
|
|
+ assert (
|
|
+ repo.get_cache_hash() == repo2.get_cache_hash()
|
|
+ ) # __env__ repos share same hash
|
|
+ assert repo.get_cache_basename() != repo2.get_cache_basename()
|
|
+ assert repo.get_linkdir() != repo2.get_linkdir()
|
|
+ assert repo.get_salt_working_dir() != repo2.get_salt_working_dir()
|
|
+ assert repo.get_cache_basename() == "master"
|
|
+ assert repo2.get_cache_basename() == "main"
|
|
+
|
|
+ assert repo.get_cache_basename() in repo.get_cachedir()
|
|
+ assert (
|
|
+ os.path.join(repo.get_cache_basehash(), repo.get_cache_basename())
|
|
+ == repo.get_cache_full_basename()
|
|
+ )
|
|
+ assert repo.get_linkdir() not in repo.get_cachedir()
|
|
+ assert repo.get_salt_working_dir() not in repo.get_cachedir()
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_env(gitpython_pillar_opts):
|
|
+ _test_env(gitpython_pillar_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_env(pygit2_pillar_opts):
|
|
+ _test_env(pygit2_pillar_opts)
|
|
+
|
|
+
|
|
+def _test_checkout_fetch_on_fail(opts):
|
|
+ p = _get_pillar(opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git")
|
|
+ p.checkout(fetch_on_fail=False) # TODO write me
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_checkout_fetch_on_fail(gitpython_pillar_opts):
|
|
+ _test_checkout_fetch_on_fail(gitpython_pillar_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_checkout_fetch_on_fail(pygit2_pillar_opts):
|
|
+ _test_checkout_fetch_on_fail(pygit2_pillar_opts)
|
|
+
|
|
+
|
|
+def _test_multiple_repos(opts):
|
|
+ p = _get_pillar(
|
|
+ opts,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "main https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "branch https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ "other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ )
|
|
+ p.checkout()
|
|
+ assert len(p.remotes) == 5
|
|
+ # make sure all repos dont share cache and working dir
|
|
+ assert len({r.get_cachedir() for r in p.remotes}) == 5
|
|
+ assert len({r.get_salt_working_dir() for r in p.remotes}) == 5
|
|
+
|
|
+ p2 = _get_pillar(
|
|
+ opts,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "main https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "branch https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ "other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ )
|
|
+ p2.checkout()
|
|
+ assert len(p2.remotes) == 5
|
|
+ # make sure that repos are given same cache dir
|
|
+ for repo, repo2 in zip(p.remotes, p2.remotes):
|
|
+ assert repo.get_cachedir() == repo2.get_cachedir()
|
|
+ assert repo.get_salt_working_dir() == repo2.get_salt_working_dir()
|
|
+ opts["pillarenv"] = "main"
|
|
+ p3 = _get_pillar(
|
|
+ opts,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "main https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "branch https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ "other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ )
|
|
+ p3.checkout()
|
|
+ # check that __env__ has different cache with different pillarenv
|
|
+ assert p.remotes[0].get_cachedir() != p3.remotes[0].get_cachedir()
|
|
+ assert p.remotes[1].get_cachedir() == p3.remotes[1].get_cachedir()
|
|
+ assert p.remotes[2].get_cachedir() == p3.remotes[2].get_cachedir()
|
|
+ assert p.remotes[3].get_cachedir() != p3.remotes[3].get_cachedir()
|
|
+ assert p.remotes[4].get_cachedir() == p3.remotes[4].get_cachedir()
|
|
+
|
|
+ # check that other branch data is in cache
|
|
+ files = set(os.listdir(p.remotes[4].get_cachedir()))
|
|
+ for f in (".gitignore", "README.md", "file.sls", "top.sls", "other_env.sls"):
|
|
+ assert f in files
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_multiple_repos(gitpython_pillar_opts):
|
|
+ _test_multiple_repos(gitpython_pillar_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_multiple_repos(pygit2_pillar_opts):
|
|
+ _test_multiple_repos(pygit2_pillar_opts)
|
|
+
|
|
+
|
|
+def _test_fetch_request(opts):
|
|
+ p = _get_pillar(
|
|
+ opts,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ )
|
|
+ frequest = os.path.join(p.remotes[0].get_salt_working_dir(), "fetch_request")
|
|
+ frequest_other = os.path.join(p.remotes[1].get_salt_working_dir(), "fetch_request")
|
|
+ opts["pillarenv"] = "main"
|
|
+ p2 = _get_pillar(
|
|
+ opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ frequest2 = os.path.join(p2.remotes[0].get_salt_working_dir(), "fetch_request")
|
|
+ assert frequest != frequest2
|
|
+ assert os.path.isfile(frequest) is False
|
|
+ assert os.path.isfile(frequest2) is False
|
|
+ assert os.path.isfile(frequest_other) is False
|
|
+ p.fetch_remotes()
|
|
+ assert os.path.isfile(frequest) is False
|
|
+ # fetch request was placed
|
|
+ assert os.path.isfile(frequest2) is True
|
|
+ p2.checkout()
|
|
+ # fetch request was found
|
|
+ assert os.path.isfile(frequest2) is False
|
|
+ p2.fetch_remotes()
|
|
+ assert os.path.isfile(frequest) is True
|
|
+ assert os.path.isfile(frequest2) is False
|
|
+ assert os.path.isfile(frequest_other) is False
|
|
+ for _ in range(3):
|
|
+ p2.fetch_remotes()
|
|
+ assert os.path.isfile(frequest) is True
|
|
+ assert os.path.isfile(frequest2) is False
|
|
+ assert os.path.isfile(frequest_other) is False
|
|
+ # fetch request should still be processed even on fetch_on_fail=False
|
|
+ p.checkout(fetch_on_fail=False)
|
|
+ assert os.path.isfile(frequest) is False
|
|
+ assert os.path.isfile(frequest2) is False
|
|
+ assert os.path.isfile(frequest_other) is False
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_fetch_request(gitpython_pillar_opts):
|
|
+ _test_fetch_request(gitpython_pillar_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_fetch_request(pygit2_pillar_opts):
|
|
+ _test_fetch_request(pygit2_pillar_opts)
|
|
+
|
|
+
|
|
+def _test_clear_old_remotes(opts):
|
|
+ p = _get_pillar(
|
|
+ opts,
|
|
+ "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ "other https://github.com/saltstack/salt-test-pillar-gitfs-2.git",
|
|
+ )
|
|
+ repo = p.remotes[0]
|
|
+ repo2 = p.remotes[1]
|
|
+ opts["pillarenv"] = "main"
|
|
+ p2 = _get_pillar(
|
|
+ opts, "__env__ https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ repo3 = p2.remotes[0]
|
|
+ assert os.path.isdir(repo.get_cachedir()) is True
|
|
+ assert os.path.isdir(repo2.get_cachedir()) is True
|
|
+ assert os.path.isdir(repo3.get_cachedir()) is True
|
|
+ p.clear_old_remotes()
|
|
+ assert os.path.isdir(repo.get_cachedir()) is True
|
|
+ assert os.path.isdir(repo2.get_cachedir()) is True
|
|
+ assert os.path.isdir(repo3.get_cachedir()) is True
|
|
+ p2.clear_old_remotes()
|
|
+ assert os.path.isdir(repo.get_cachedir()) is True
|
|
+ assert os.path.isdir(repo2.get_cachedir()) is False
|
|
+ assert os.path.isdir(repo3.get_cachedir()) is True
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_clear_old_remotes(gitpython_pillar_opts):
|
|
+ _test_clear_old_remotes(gitpython_pillar_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_clear_old_remotes(pygit2_pillar_opts):
|
|
+ _test_clear_old_remotes(pygit2_pillar_opts)
|
|
+
|
|
+
|
|
+def _test_remote_map(opts):
|
|
+ p = _get_pillar(
|
|
+ opts,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ p.fetch_remotes()
|
|
+ assert len(p.remotes) == 1
|
|
+ assert os.path.isfile(
|
|
+ os.path.join(opts["cachedir"], "git_pillar", "remote_map.txt")
|
|
+ )
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_remote_map(gitpython_pillar_opts):
|
|
+ _test_remote_map(gitpython_pillar_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_remote_map(pygit2_pillar_opts):
|
|
+ _test_remote_map(pygit2_pillar_opts)
|
|
+
|
|
+
|
|
+def _test_lock(opts):
|
|
+ p = _get_pillar(
|
|
+ opts,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ p.fetch_remotes()
|
|
+ assert len(p.remotes) == 1
|
|
+ repo = p.remotes[0]
|
|
+ assert repo.get_salt_working_dir() in repo._get_lock_file()
|
|
+ assert repo.lock() == (
|
|
+ [
|
|
+ "Set update lock for git_pillar remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'"
|
|
+ ],
|
|
+ [],
|
|
+ )
|
|
+ assert os.path.isfile(repo._get_lock_file())
|
|
+ assert repo.clear_lock() == (
|
|
+ [
|
|
+ "Removed update lock for git_pillar remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'"
|
|
+ ],
|
|
+ [],
|
|
+ )
|
|
+ assert not os.path.isfile(repo._get_lock_file())
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_lock(gitpython_pillar_opts):
|
|
+ _test_lock(gitpython_pillar_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_lock(pygit2_pillar_opts):
|
|
+ _test_lock(pygit2_pillar_opts)
|
|
diff --git a/tests/pytests/functional/utils/test_winrepo.py b/tests/pytests/functional/utils/test_winrepo.py
|
|
new file mode 100644
|
|
index 0000000000..117d995bba
|
|
--- /dev/null
|
|
+++ b/tests/pytests/functional/utils/test_winrepo.py
|
|
@@ -0,0 +1,164 @@
|
|
+import os
|
|
+
|
|
+import pytest
|
|
+
|
|
+from salt.runners.winrepo import GLOBAL_ONLY, PER_REMOTE_ONLY, PER_REMOTE_OVERRIDES
|
|
+from salt.utils.gitfs import GitPython, Pygit2, WinRepo
|
|
+from salt.utils.immutabletypes import ImmutableDict, ImmutableList
|
|
+
|
|
+pytestmark = [
|
|
+ pytest.mark.slow_test,
|
|
+]
|
|
+
|
|
+
|
|
+try:
|
|
+ import git # pylint: disable=unused-import
|
|
+
|
|
+ HAS_GITPYTHON = True
|
|
+except ImportError:
|
|
+ HAS_GITPYTHON = False
|
|
+
|
|
+
|
|
+try:
|
|
+ import pygit2 # pylint: disable=unused-import
|
|
+
|
|
+ HAS_PYGIT2 = True
|
|
+except ImportError:
|
|
+ HAS_PYGIT2 = False
|
|
+
|
|
+
|
|
+skipif_no_gitpython = pytest.mark.skipif(not HAS_GITPYTHON, reason="Missing gitpython")
|
|
+skipif_no_pygit2 = pytest.mark.skipif(not HAS_PYGIT2, reason="Missing pygit2")
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def winrepo_opts(salt_factories, tmp_path):
|
|
+ config_defaults = {"cachedir": str(tmp_path)}
|
|
+ factory = salt_factories.salt_master_daemon(
|
|
+ "winrepo-functional-master", defaults=config_defaults
|
|
+ )
|
|
+ config_defaults = dict(factory.config)
|
|
+ for key, item in config_defaults.items():
|
|
+ if isinstance(item, ImmutableDict):
|
|
+ config_defaults[key] = dict(item)
|
|
+ elif isinstance(item, ImmutableList):
|
|
+ config_defaults[key] = list(item)
|
|
+ return config_defaults
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def gitpython_winrepo_opts(winrepo_opts):
|
|
+ winrepo_opts["verified_winrepo_provider"] = "gitpython"
|
|
+ return winrepo_opts
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def pygit2_winrepo_opts(winrepo_opts):
|
|
+ winrepo_opts["verified_winrepo_provider"] = "pygit2"
|
|
+ return winrepo_opts
|
|
+
|
|
+
|
|
+def _get_winrepo(opts, *remotes):
|
|
+ return WinRepo(
|
|
+ opts,
|
|
+ remotes,
|
|
+ per_remote_overrides=PER_REMOTE_OVERRIDES,
|
|
+ per_remote_only=PER_REMOTE_ONLY,
|
|
+ global_only=GLOBAL_ONLY,
|
|
+ )
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_winrepo_provider(gitpython_winrepo_opts):
|
|
+ w = _get_winrepo(
|
|
+ gitpython_winrepo_opts,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ assert len(w.remotes) == 1
|
|
+ assert w.provider == "gitpython"
|
|
+ assert isinstance(w.remotes[0], GitPython)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_winrepo_provider(pygit2_winrepo_opts):
|
|
+ w = _get_winrepo(
|
|
+ pygit2_winrepo_opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git"
|
|
+ )
|
|
+ assert len(w.remotes) == 1
|
|
+ assert w.provider == "pygit2"
|
|
+ assert isinstance(w.remotes[0], Pygit2)
|
|
+
|
|
+
|
|
+def _test_winrepo_simple(opts):
|
|
+ w = _get_winrepo(opts, "https://github.com/saltstack/salt-test-pillar-gitfs.git")
|
|
+ assert len(w.remotes) == 1
|
|
+ w.checkout()
|
|
+ repo = w.remotes[0]
|
|
+ files = set(os.listdir(repo.get_cachedir()))
|
|
+ for f in (".gitignore", "README.md", "file.sls", "top.sls"):
|
|
+ assert f in files
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_winrepo_simple(gitpython_winrepo_opts):
|
|
+ _test_winrepo_simple(gitpython_winrepo_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_winrepo_simple(pygit2_winrepo_opts):
|
|
+ _test_winrepo_simple(pygit2_winrepo_opts)
|
|
+
|
|
+
|
|
+def _test_remote_map(opts):
|
|
+ p = _get_winrepo(
|
|
+ opts,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ p.fetch_remotes()
|
|
+ assert len(p.remotes) == 1
|
|
+ assert os.path.isfile(os.path.join(opts["cachedir"], "winrepo", "remote_map.txt"))
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_remote_map(gitpython_winrepo_opts):
|
|
+ _test_remote_map(gitpython_winrepo_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_remote_map(pygit2_winrepo_opts):
|
|
+ _test_remote_map(pygit2_winrepo_opts)
|
|
+
|
|
+
|
|
+def _test_lock(opts):
|
|
+ w = _get_winrepo(
|
|
+ opts,
|
|
+ "https://github.com/saltstack/salt-test-pillar-gitfs.git",
|
|
+ )
|
|
+ w.fetch_remotes()
|
|
+ assert len(w.remotes) == 1
|
|
+ repo = w.remotes[0]
|
|
+ assert repo.get_salt_working_dir() in repo._get_lock_file()
|
|
+ assert repo.lock() == (
|
|
+ [
|
|
+ "Set update lock for winrepo remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'"
|
|
+ ],
|
|
+ [],
|
|
+ )
|
|
+ assert os.path.isfile(repo._get_lock_file())
|
|
+ assert repo.clear_lock() == (
|
|
+ [
|
|
+ "Removed update lock for winrepo remote 'https://github.com/saltstack/salt-test-pillar-gitfs.git'"
|
|
+ ],
|
|
+ [],
|
|
+ )
|
|
+ assert not os.path.isfile(repo._get_lock_file())
|
|
+
|
|
+
|
|
+@skipif_no_gitpython
|
|
+def test_gitpython_lock(gitpython_winrepo_opts):
|
|
+ _test_lock(gitpython_winrepo_opts)
|
|
+
|
|
+
|
|
+@skipif_no_pygit2
|
|
+def test_pygit2_lock(pygit2_winrepo_opts):
|
|
+ _test_lock(pygit2_winrepo_opts)
|
|
diff --git a/tests/pytests/unit/test_minion.py b/tests/pytests/unit/test_minion.py
|
|
index 4508eaee95..740743194e 100644
|
|
--- a/tests/pytests/unit/test_minion.py
|
|
+++ b/tests/pytests/unit/test_minion.py
|
|
@@ -21,35 +21,33 @@ from tests.support.mock import MagicMock, patch
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
-def test_minion_load_grains_false():
|
|
+def test_minion_load_grains_false(minion_opts):
|
|
"""
|
|
Minion does not generate grains when load_grains is False
|
|
"""
|
|
- opts = {"random_startup_delay": 0, "grains": {"foo": "bar"}}
|
|
+ minion_opts["grains"] = {"foo": "bar"}
|
|
with patch("salt.loader.grains") as grainsfunc:
|
|
- minion = salt.minion.Minion(opts, load_grains=False)
|
|
- assert minion.opts["grains"] == opts["grains"]
|
|
+ minion = salt.minion.Minion(minion_opts, load_grains=False)
|
|
+ assert minion.opts["grains"] == minion_opts["grains"]
|
|
grainsfunc.assert_not_called()
|
|
|
|
|
|
-def test_minion_load_grains_true():
|
|
+def test_minion_load_grains_true(minion_opts):
|
|
"""
|
|
Minion generates grains when load_grains is True
|
|
"""
|
|
- opts = {"random_startup_delay": 0, "grains": {}}
|
|
with patch("salt.loader.grains") as grainsfunc:
|
|
- minion = salt.minion.Minion(opts, load_grains=True)
|
|
+ minion = salt.minion.Minion(minion_opts, load_grains=True)
|
|
assert minion.opts["grains"] != {}
|
|
grainsfunc.assert_called()
|
|
|
|
|
|
-def test_minion_load_grains_default():
|
|
+def test_minion_load_grains_default(minion_opts):
|
|
"""
|
|
Minion load_grains defaults to True
|
|
"""
|
|
- opts = {"random_startup_delay": 0, "grains": {}}
|
|
with patch("salt.loader.grains") as grainsfunc:
|
|
- minion = salt.minion.Minion(opts)
|
|
+ minion = salt.minion.Minion(minion_opts)
|
|
assert minion.opts["grains"] != {}
|
|
grainsfunc.assert_called()
|
|
|
|
@@ -91,24 +89,17 @@ def test_send_req_tries(req_channel, minion_opts):
|
|
|
|
assert rtn == 30
|
|
|
|
-
|
|
-@patch("salt.channel.client.ReqChannel.factory")
|
|
-def test_mine_send_tries(req_channel_factory):
|
|
+def test_mine_send_tries(minion_opts):
|
|
channel_enter = MagicMock()
|
|
channel_enter.send.side_effect = lambda load, timeout, tries: tries
|
|
channel = MagicMock()
|
|
channel.__enter__.return_value = channel_enter
|
|
|
|
- opts = {
|
|
- "random_startup_delay": 0,
|
|
- "grains": {},
|
|
- "return_retry_tries": 20,
|
|
- "minion_sign_messages": False,
|
|
- }
|
|
+ minion_opts["return_retry_tries"] = 20
|
|
with patch("salt.channel.client.ReqChannel.factory", return_value=channel), patch(
|
|
"salt.loader.grains"
|
|
):
|
|
- minion = salt.minion.Minion(opts)
|
|
+ minion = salt.minion.Minion(minion_opts)
|
|
minion.tok = "token"
|
|
|
|
data = {}
|
|
diff --git a/tests/pytests/unit/utils/test_gitfs.py b/tests/pytests/unit/utils/test_gitfs.py
|
|
index e9915de412..2bf627049f 100644
|
|
--- a/tests/pytests/unit/utils/test_gitfs.py
|
|
+++ b/tests/pytests/unit/utils/test_gitfs.py
|
|
@@ -1,5 +1,4 @@
|
|
import os
|
|
-import string
|
|
import time
|
|
|
|
import pytest
|
|
@@ -214,11 +213,11 @@ def test_checkout_pygit2(_prepare_provider):
|
|
provider.init_remote()
|
|
provider.fetch()
|
|
provider.branch = "master"
|
|
- assert provider.cachedir in provider.checkout()
|
|
+ assert provider.get_cachedir() in provider.checkout()
|
|
provider.branch = "simple_tag"
|
|
- assert provider.cachedir in provider.checkout()
|
|
+ assert provider.get_cachedir() in provider.checkout()
|
|
provider.branch = "annotated_tag"
|
|
- assert provider.cachedir in provider.checkout()
|
|
+ assert provider.get_cachedir() in provider.checkout()
|
|
provider.branch = "does_not_exist"
|
|
assert provider.checkout() is None
|
|
|
|
@@ -238,18 +237,9 @@ def test_checkout_pygit2_with_home_env_unset(_prepare_provider):
|
|
assert "HOME" in os.environ
|
|
|
|
|
|
-def test_full_id_pygit2(_prepare_provider):
|
|
- assert _prepare_provider.full_id().startswith("-")
|
|
- assert _prepare_provider.full_id().endswith("/pygit2-repo---gitfs-master--")
|
|
-
|
|
-
|
|
@pytest.mark.skipif(not HAS_PYGIT2, reason="This host lacks proper pygit2 support")
|
|
@pytest.mark.skip_on_windows(
|
|
reason="Skip Pygit2 on windows, due to pygit2 access error on windows"
|
|
)
|
|
def test_get_cachedir_basename_pygit2(_prepare_provider):
|
|
- basename = _prepare_provider.get_cachedir_basename()
|
|
- assert len(basename) == 45
|
|
- assert basename[0] == "-"
|
|
- # check that a valid base64 is given '/' -> '_'
|
|
- assert all(c in string.ascii_letters + string.digits + "+_=" for c in basename[1:])
|
|
+ assert "_" == _prepare_provider.get_cache_basename()
|
|
diff --git a/tests/unit/utils/test_gitfs.py b/tests/unit/utils/test_gitfs.py
|
|
index 6d8e97a239..259ea056fc 100644
|
|
--- a/tests/unit/utils/test_gitfs.py
|
|
+++ b/tests/unit/utils/test_gitfs.py
|
|
@@ -114,27 +114,14 @@ class TestGitBase(TestCase, AdaptedConfigurationTestCaseMixin):
|
|
self.assertTrue(self.main_class.remotes[0].fetched)
|
|
self.assertFalse(self.main_class.remotes[1].fetched)
|
|
|
|
- def test_full_id(self):
|
|
- self.assertEqual(
|
|
- self.main_class.remotes[0].full_id(), "-file://repo1.git---gitfs-master--"
|
|
- )
|
|
-
|
|
- def test_full_id_with_name(self):
|
|
- self.assertEqual(
|
|
- self.main_class.remotes[1].full_id(),
|
|
- "repo2-file://repo2.git---gitfs-master--",
|
|
- )
|
|
-
|
|
def test_get_cachedir_basename(self):
|
|
self.assertEqual(
|
|
- self.main_class.remotes[0].get_cachedir_basename(),
|
|
- "-jXhnbGDemchtZwTwaD2s6VOaVvs98a7w+AtiYlmOVb0=",
|
|
+ self.main_class.remotes[0].get_cache_basename(),
|
|
+ "_",
|
|
)
|
|
-
|
|
- def test_get_cachedir_base_with_name(self):
|
|
self.assertEqual(
|
|
- self.main_class.remotes[1].get_cachedir_basename(),
|
|
- "repo2-nuezpiDtjQRFC0ZJDByvi+F6Vb8ZhfoH41n_KFxTGsU=",
|
|
+ self.main_class.remotes[1].get_cache_basename(),
|
|
+ "_",
|
|
)
|
|
|
|
def test_git_provider_mp_lock(self):
|
|
--
|
|
2.42.0
|
|
|
|
|