test_gitfs.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. """
  2. These only test the provider selection and verification logic, they do not init
  3. any remotes.
  4. """
  5. import os
  6. import shutil
  7. from time import time
  8. import salt.fileserver.gitfs
  9. import salt.utils.files
  10. import salt.utils.gitfs
  11. import salt.utils.platform
  12. import tests.support.paths
  13. from salt.exceptions import FileserverConfigError
  14. from tests.support.mixins import AdaptedConfigurationTestCaseMixin
  15. from tests.support.mock import MagicMock, patch
  16. from tests.support.unit import TestCase, skipIf
  17. try:
  18. HAS_PYGIT2 = (
  19. salt.utils.gitfs.PYGIT2_VERSION >= salt.utils.gitfs.PYGIT2_MINVER
  20. and salt.utils.gitfs.LIBGIT2_VERSION >= salt.utils.gitfs.LIBGIT2_MINVER
  21. )
  22. except AttributeError:
  23. HAS_PYGIT2 = False
  24. if HAS_PYGIT2:
  25. import pygit2
  26. class TestGitBase(TestCase, AdaptedConfigurationTestCaseMixin):
  27. def setUp(self):
  28. class MockedProvider(
  29. salt.utils.gitfs.GitProvider
  30. ): # pylint: disable=abstract-method
  31. def __init__(
  32. self,
  33. opts,
  34. remote,
  35. per_remote_defaults,
  36. per_remote_only,
  37. override_params,
  38. cache_root,
  39. role="gitfs",
  40. ):
  41. self.provider = "mocked"
  42. self.fetched = False
  43. super().__init__(
  44. opts,
  45. remote,
  46. per_remote_defaults,
  47. per_remote_only,
  48. override_params,
  49. cache_root,
  50. role,
  51. )
  52. def init_remote(self):
  53. self.repo = True
  54. new = False
  55. return new
  56. def envs(self):
  57. return ["base"]
  58. def fetch(self):
  59. self.fetched = True
  60. git_providers = {
  61. "mocked": MockedProvider,
  62. }
  63. gitfs_remotes = ["file://repo1.git", {"file://repo2.git": [{"name": "repo2"}]}]
  64. self.opts = self.get_temp_config(
  65. "master", gitfs_remotes=gitfs_remotes, verified_gitfs_provider="mocked"
  66. )
  67. self.main_class = salt.utils.gitfs.GitFS(
  68. self.opts,
  69. self.opts["gitfs_remotes"],
  70. per_remote_overrides=salt.fileserver.gitfs.PER_REMOTE_OVERRIDES,
  71. per_remote_only=salt.fileserver.gitfs.PER_REMOTE_ONLY,
  72. git_providers=git_providers,
  73. )
  74. def tearDown(self):
  75. # Providers are preserved with GitFS's instance_map
  76. for remote in self.main_class.remotes:
  77. remote.fetched = False
  78. del self.main_class
  79. def test_update_all(self):
  80. self.main_class.update()
  81. self.assertEqual(len(self.main_class.remotes), 2, "Wrong number of remotes")
  82. self.assertTrue(self.main_class.remotes[0].fetched)
  83. self.assertTrue(self.main_class.remotes[1].fetched)
  84. def test_update_by_name(self):
  85. self.main_class.update("repo2")
  86. self.assertEqual(len(self.main_class.remotes), 2, "Wrong number of remotes")
  87. self.assertFalse(self.main_class.remotes[0].fetched)
  88. self.assertTrue(self.main_class.remotes[1].fetched)
  89. def test_update_by_id_and_name(self):
  90. self.main_class.update([("file://repo1.git", None)])
  91. self.assertEqual(len(self.main_class.remotes), 2, "Wrong number of remotes")
  92. self.assertTrue(self.main_class.remotes[0].fetched)
  93. self.assertFalse(self.main_class.remotes[1].fetched)
  94. class TestGitFSProvider(TestCase):
  95. def setUp(self):
  96. self.opts = {"cachedir": "/tmp/gitfs-test-cache"}
  97. def tearDown(self):
  98. self.opts = None
  99. def test_provider_case_insensitive(self):
  100. """
  101. Ensure that both lowercase and non-lowercase values are supported
  102. """
  103. provider = "GitPython"
  104. for role_name, role_class in (
  105. ("gitfs", salt.utils.gitfs.GitFS),
  106. ("git_pillar", salt.utils.gitfs.GitPillar),
  107. ("winrepo", salt.utils.gitfs.WinRepo),
  108. ):
  109. key = "{}_provider".format(role_name)
  110. with patch.object(
  111. role_class, "verify_gitpython", MagicMock(return_value=True)
  112. ):
  113. with patch.object(
  114. role_class, "verify_pygit2", MagicMock(return_value=False)
  115. ):
  116. args = [self.opts, {}]
  117. kwargs = {"init_remotes": False}
  118. if role_name == "winrepo":
  119. kwargs["cache_root"] = "/tmp/winrepo-dir"
  120. with patch.dict(self.opts, {key: provider}):
  121. # Try to create an instance with uppercase letters in
  122. # provider name. If it fails then a
  123. # FileserverConfigError will be raised, so no assert is
  124. # necessary.
  125. role_class(*args, **kwargs)
  126. # Now try to instantiate an instance with all lowercase
  127. # letters. Again, no need for an assert here.
  128. role_class(*args, **kwargs)
  129. def test_valid_provider(self):
  130. """
  131. Ensure that an invalid provider is not accepted, raising a
  132. FileserverConfigError.
  133. """
  134. def _get_mock(verify, provider):
  135. """
  136. Return a MagicMock with the desired return value
  137. """
  138. return MagicMock(return_value=verify.endswith(provider))
  139. for role_name, role_class in (
  140. ("gitfs", salt.utils.gitfs.GitFS),
  141. ("git_pillar", salt.utils.gitfs.GitPillar),
  142. ("winrepo", salt.utils.gitfs.WinRepo),
  143. ):
  144. key = "{}_provider".format(role_name)
  145. for provider in salt.utils.gitfs.GIT_PROVIDERS:
  146. verify = "verify_gitpython"
  147. mock1 = _get_mock(verify, provider)
  148. with patch.object(role_class, verify, mock1):
  149. verify = "verify_pygit2"
  150. mock2 = _get_mock(verify, provider)
  151. with patch.object(role_class, verify, mock2):
  152. args = [self.opts, {}]
  153. kwargs = {"init_remotes": False}
  154. if role_name == "winrepo":
  155. kwargs["cache_root"] = "/tmp/winrepo-dir"
  156. with patch.dict(self.opts, {key: provider}):
  157. role_class(*args, **kwargs)
  158. with patch.dict(self.opts, {key: "foo"}):
  159. # Set the provider name to a known invalid provider
  160. # and make sure it raises an exception.
  161. self.assertRaises(
  162. FileserverConfigError, role_class, *args, **kwargs
  163. )
  164. @skipIf(not HAS_PYGIT2, "This host lacks proper pygit2 support")
  165. @skipIf(
  166. salt.utils.platform.is_windows(),
  167. "Skip Pygit2 on windows, due to pygit2 access error on windows",
  168. )
  169. class TestPygit2(TestCase):
  170. def _prepare_remote_repository(self, path):
  171. shutil.rmtree(path, ignore_errors=True)
  172. filecontent = "This is an empty README file"
  173. filename = "README"
  174. signature = pygit2.Signature(
  175. "Dummy Commiter", "dummy@dummy.com", int(time()), 0
  176. )
  177. repository = pygit2.init_repository(path, False)
  178. builder = repository.TreeBuilder()
  179. tree = builder.write()
  180. commit = repository.create_commit(
  181. "HEAD", signature, signature, "Create master branch", tree, []
  182. )
  183. repository.create_reference("refs/tags/simple_tag", commit)
  184. with salt.utils.files.fopen(
  185. os.path.join(repository.workdir, filename), "w"
  186. ) as file:
  187. file.write(filecontent)
  188. blob = repository.create_blob_fromworkdir(filename)
  189. builder = repository.TreeBuilder()
  190. builder.insert(filename, blob, pygit2.GIT_FILEMODE_BLOB)
  191. tree = builder.write()
  192. repository.index.read()
  193. repository.index.add(filename)
  194. repository.index.write()
  195. commit = repository.create_commit(
  196. "HEAD",
  197. signature,
  198. signature,
  199. "Added a README",
  200. tree,
  201. [repository.head.target],
  202. )
  203. repository.create_tag(
  204. "annotated_tag", commit, pygit2.GIT_OBJ_COMMIT, signature, "some message"
  205. )
  206. def _prepare_cache_repository(self, remote, cache):
  207. opts = {
  208. "cachedir": cache,
  209. "__role": "minion",
  210. "gitfs_disable_saltenv_mapping": False,
  211. "gitfs_base": "master",
  212. "gitfs_insecure_auth": False,
  213. "gitfs_mountpoint": "",
  214. "gitfs_passphrase": "",
  215. "gitfs_password": "",
  216. "gitfs_privkey": "",
  217. "gitfs_provider": "pygit2",
  218. "gitfs_pubkey": "",
  219. "gitfs_ref_types": ["branch", "tag", "sha"],
  220. "gitfs_refspecs": [
  221. "+refs/heads/*:refs/remotes/origin/*",
  222. "+refs/tags/*:refs/tags/*",
  223. ],
  224. "gitfs_root": "",
  225. "gitfs_saltenv_blacklist": [],
  226. "gitfs_saltenv_whitelist": [],
  227. "gitfs_ssl_verify": True,
  228. "gitfs_update_interval": 3,
  229. "gitfs_user": "",
  230. "verified_gitfs_provider": "pygit2",
  231. }
  232. per_remote_defaults = {
  233. "base": "master",
  234. "disable_saltenv_mapping": False,
  235. "insecure_auth": False,
  236. "ref_types": ["branch", "tag", "sha"],
  237. "passphrase": "",
  238. "mountpoint": "",
  239. "password": "",
  240. "privkey": "",
  241. "pubkey": "",
  242. "refspecs": [
  243. "+refs/heads/*:refs/remotes/origin/*",
  244. "+refs/tags/*:refs/tags/*",
  245. ],
  246. "root": "",
  247. "saltenv_blacklist": [],
  248. "saltenv_whitelist": [],
  249. "ssl_verify": True,
  250. "update_interval": 60,
  251. "user": "",
  252. }
  253. per_remote_only = ("all_saltenvs", "name", "saltenv")
  254. override_params = tuple(per_remote_defaults.keys())
  255. cache_root = os.path.join(cache, "gitfs")
  256. role = "gitfs"
  257. shutil.rmtree(cache_root, ignore_errors=True)
  258. provider = salt.utils.gitfs.Pygit2(
  259. opts,
  260. remote,
  261. per_remote_defaults,
  262. per_remote_only,
  263. override_params,
  264. cache_root,
  265. role,
  266. )
  267. return provider
  268. def test_checkout(self):
  269. remote = os.path.join(tests.support.paths.TMP, "pygit2-repo")
  270. cache = os.path.join(tests.support.paths.TMP, "pygit2-repo-cache")
  271. self._prepare_remote_repository(remote)
  272. provider = self._prepare_cache_repository(remote, cache)
  273. provider.remotecallbacks = None
  274. provider.credentials = None
  275. provider.init_remote()
  276. provider.fetch()
  277. provider.branch = "master"
  278. self.assertIn(provider.cachedir, provider.checkout())
  279. provider.branch = "simple_tag"
  280. self.assertIn(provider.cachedir, provider.checkout())
  281. provider.branch = "annotated_tag"
  282. self.assertIn(provider.cachedir, provider.checkout())
  283. provider.branch = "does_not_exist"
  284. self.assertIsNone(provider.checkout())