gitfs.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. """
  2. Base classes for gitfs/git_pillar integration tests
  3. """
  4. import copy
  5. import errno
  6. import logging
  7. import os
  8. import pathlib
  9. import pprint
  10. import shutil
  11. import tempfile
  12. import textwrap
  13. import salt.utils.files
  14. import salt.utils.path
  15. import salt.utils.platform
  16. import salt.utils.yaml
  17. from salt.fileserver import gitfs
  18. from salt.pillar import git_pillar
  19. from saltfactories.utils.ports import get_unused_localhost_port
  20. from saltfactories.utils.processes.bases import FactoryDaemonScriptBase
  21. from saltfactories.utils.processes.helpers import start_daemon, terminate_process
  22. from saltfactories.utils.processes.sshd import SshdDaemon
  23. from tests.support.case import ModuleCase
  24. from tests.support.helpers import patched_environ, requires_system_grains
  25. from tests.support.mixins import (
  26. AdaptedConfigurationTestCaseMixin,
  27. LoaderModuleMockMixin,
  28. SaltReturnAssertsMixin,
  29. )
  30. from tests.support.mock import patch
  31. from tests.support.runtests import RUNTIME_VARS
  32. from tests.support.unit import SkipTest
  33. try:
  34. import psutil
  35. except ImportError:
  36. pass
  37. log = logging.getLogger(__name__)
  38. USERNAME = "gitpillaruser"
  39. PASSWORD = "saltrules"
  40. _OPTS = {
  41. "__role": "minion",
  42. "environment": None,
  43. "pillarenv": None,
  44. "hash_type": "sha256",
  45. "file_roots": {},
  46. "state_top": "top.sls",
  47. "state_top_saltenv": None,
  48. "renderer": "yaml_jinja",
  49. "renderer_whitelist": [],
  50. "renderer_blacklist": [],
  51. "pillar_merge_lists": False,
  52. "git_pillar_base": "master",
  53. "git_pillar_branch": "master",
  54. "git_pillar_env": "",
  55. "git_pillar_fallback": "",
  56. "git_pillar_root": "",
  57. "git_pillar_ssl_verify": True,
  58. "git_pillar_global_lock": True,
  59. "git_pillar_user": "",
  60. "git_pillar_password": "",
  61. "git_pillar_insecure_auth": False,
  62. "git_pillar_privkey": "",
  63. "git_pillar_pubkey": "",
  64. "git_pillar_passphrase": "",
  65. "git_pillar_refspecs": [
  66. "+refs/heads/*:refs/remotes/origin/*",
  67. "+refs/tags/*:refs/tags/*",
  68. ],
  69. "git_pillar_includes": True,
  70. }
  71. PROC_TIMEOUT = 10
  72. class UwsgiDaemon(FactoryDaemonScriptBase):
  73. def __init__(self, *args, **kwargs):
  74. config_dir = kwargs.pop("config_dir")
  75. check_port = kwargs.pop("check_port")
  76. super().__init__(*args, **kwargs)
  77. self.config_dir = config_dir
  78. self.check_port = check_port
  79. def get_log_prefix(self):
  80. return "[uWSGI] "
  81. def get_base_script_args(self):
  82. """
  83. Returns any additional arguments to pass to the CLI script
  84. """
  85. return ["--yaml", os.path.join(self.config_dir, "uwsgi.yml")]
  86. def get_check_ports(self):
  87. """
  88. Return a list of ports to check against to ensure the daemon is running
  89. """
  90. return [self.check_port]
  91. class NginxDaemon(FactoryDaemonScriptBase):
  92. def __init__(self, *args, **kwargs):
  93. config_dir = kwargs.pop("config_dir")
  94. check_port = kwargs.pop("check_port")
  95. super().__init__(*args, **kwargs)
  96. self.config_dir = config_dir
  97. self.check_port = check_port
  98. def get_log_prefix(self):
  99. return "[Nginx] "
  100. def get_base_script_args(self):
  101. """
  102. Returns any additional arguments to pass to the CLI script
  103. """
  104. return ["-c", os.path.join(self.config_dir, "nginx.conf")]
  105. def get_check_ports(self):
  106. """
  107. Return a list of ports to check against to ensure the daemon is running
  108. """
  109. return [self.check_port]
  110. class SaltClientMixin(ModuleCase):
  111. client = None
  112. @classmethod
  113. @requires_system_grains
  114. def setUpClass(cls, grains=None): # pylint: disable=arguments-differ
  115. # Cent OS 6 has too old a version of git to handle the make_repo code, as
  116. # it lacks the -c option for git itself.
  117. make_repo = getattr(cls, "make_repo", None)
  118. if (
  119. callable(make_repo)
  120. and grains["os_family"] == "RedHat"
  121. and grains["osmajorrelease"] < 7
  122. ):
  123. raise SkipTest("RHEL < 7 has too old a version of git to run these tests")
  124. # Late import
  125. import salt.client
  126. mopts = AdaptedConfigurationTestCaseMixin.get_config(
  127. "master", from_scratch=True
  128. )
  129. cls.user = mopts["user"]
  130. cls.client = salt.client.get_local_client(mopts=mopts)
  131. @classmethod
  132. def tearDownClass(cls):
  133. cls.client = None
  134. @classmethod
  135. def cls_run_function(cls, function, *args, **kwargs):
  136. orig = cls.client.cmd("minion", function, arg=args, timeout=300, kwarg=kwargs)
  137. return orig["minion"]
  138. class SSHDMixin(SaltClientMixin, SaltReturnAssertsMixin):
  139. """
  140. Functions to stand up an SSHD server to serve up git repos for tests.
  141. """
  142. sshd_proc = None
  143. prep_states_ran = False
  144. known_hosts_setup = False
  145. @classmethod
  146. def setUpClass(cls): # pylint: disable=arguments-differ
  147. super().setUpClass()
  148. try:
  149. log.info("%s: prep_server()", cls.__name__)
  150. cls.sshd_bin = salt.utils.path.which("sshd")
  151. cls.sshd_config_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  152. cls.sshd_config = os.path.join(cls.sshd_config_dir, "sshd_config")
  153. cls.sshd_port = get_unused_localhost_port(cached_seconds=120)
  154. cls.url = "ssh://{username}@127.0.0.1:{port}/~/repo.git".format(
  155. username=cls.username, port=cls.sshd_port
  156. )
  157. cls.url_extra_repo = "ssh://{username}@127.0.0.1:{port}/~/extra_repo.git".format(
  158. username=cls.username, port=cls.sshd_port
  159. )
  160. home = "/root/.ssh"
  161. cls.ext_opts = {
  162. "url": cls.url,
  163. "url_extra_repo": cls.url_extra_repo,
  164. "privkey_nopass": os.path.join(home, cls.id_rsa_nopass),
  165. "pubkey_nopass": os.path.join(home, cls.id_rsa_nopass + ".pub"),
  166. "privkey_withpass": os.path.join(home, cls.id_rsa_withpass),
  167. "pubkey_withpass": os.path.join(home, cls.id_rsa_withpass + ".pub"),
  168. "passphrase": cls.passphrase,
  169. }
  170. if cls.prep_states_ran is False:
  171. ret = cls.cls_run_function(
  172. "state.apply",
  173. mods="git_pillar.ssh",
  174. pillar={
  175. "git_pillar": {
  176. "git_ssh": cls.git_ssh,
  177. "id_rsa_nopass": cls.id_rsa_nopass,
  178. "id_rsa_withpass": cls.id_rsa_withpass,
  179. "sshd_bin": cls.sshd_bin,
  180. "sshd_port": cls.sshd_port,
  181. "sshd_config_dir": cls.sshd_config_dir,
  182. "master_user": cls.user,
  183. "user": cls.username,
  184. }
  185. },
  186. )
  187. assert next(iter(ret.values()))["result"] is True
  188. cls.prep_states_ran = True
  189. log.info("%s: States applied", cls.__name__)
  190. if cls.sshd_proc is not None:
  191. if not psutil.pid_exists(cls.sshd_proc.pid):
  192. log.info(
  193. "%s: sshd started but appears to be dead now. Will try to restart it.",
  194. cls.__name__,
  195. )
  196. cls.sshd_proc = None
  197. if cls.sshd_proc is None:
  198. cls.sshd_proc = start_daemon(
  199. cls.sshd_bin,
  200. SshdDaemon,
  201. config_dir=pathlib.Path(cls.sshd_config_dir),
  202. serve_port=cls.sshd_port,
  203. )
  204. log.info("%s: sshd started", cls.__name__)
  205. except AssertionError:
  206. cls.tearDownClass()
  207. raise
  208. if cls.known_hosts_setup is False:
  209. known_hosts_ret = cls.cls_run_function(
  210. "ssh.set_known_host",
  211. user=cls.user,
  212. hostname="127.0.0.1",
  213. port=cls.sshd_port,
  214. enc="ssh-rsa",
  215. fingerprint="fd:6f:7f:5d:06:6b:f2:06:0d:26:93:9e:5a:b5:19:46",
  216. hash_known_hosts=False,
  217. fingerprint_hash_type="md5",
  218. )
  219. if "error" in known_hosts_ret:
  220. cls.tearDownClass()
  221. raise AssertionError(
  222. "Failed to add key to {} user's known_hosts "
  223. "file: {}".format(cls.master_opts["user"], known_hosts_ret["error"])
  224. )
  225. cls.known_hosts_setup = True
  226. @classmethod
  227. def tearDownClass(cls):
  228. if cls.sshd_proc is not None:
  229. log.info(
  230. "[%s] Stopping %s",
  231. cls.sshd_proc.get_log_prefix(),
  232. cls.sshd_proc.__class__.__name__,
  233. )
  234. terminate_process(cls.sshd_proc.pid, kill_children=True, slow_stop=True)
  235. log.info(
  236. "[%s] %s stopped",
  237. cls.sshd_proc.get_log_prefix(),
  238. cls.sshd_proc.__class__.__name__,
  239. )
  240. cls.sshd_proc = None
  241. if cls.prep_states_ran:
  242. ret = cls.cls_run_function(
  243. "state.single", "user.absent", name=cls.username, purge=True
  244. )
  245. try:
  246. if ret and "minion" in ret:
  247. ret_data = next(iter(ret["minion"].values()))
  248. if not ret_data["result"]:
  249. log.warning("Failed to delete test account %s", cls.username)
  250. except KeyError:
  251. log.warning(
  252. "Failed to delete test account. Salt return:\n%s",
  253. pprint.pformat(ret),
  254. )
  255. cls.prep_states_ran = False
  256. cls.known_hosts_setup = False
  257. shutil.rmtree(cls.sshd_config_dir, ignore_errors=True)
  258. ssh_dir = os.path.expanduser("~/.ssh")
  259. for filename in (
  260. cls.id_rsa_nopass,
  261. cls.id_rsa_nopass + ".pub",
  262. cls.id_rsa_withpass,
  263. cls.id_rsa_withpass + ".pub",
  264. cls.git_ssh,
  265. ):
  266. try:
  267. os.remove(os.path.join(ssh_dir, filename))
  268. except OSError as exc:
  269. if exc.errno != errno.ENOENT:
  270. raise
  271. super().tearDownClass()
  272. class WebserverMixin(SaltClientMixin, SaltReturnAssertsMixin):
  273. """
  274. Functions to stand up an nginx + uWSGI + git-http-backend webserver to
  275. serve up git repos for tests.
  276. """
  277. nginx_proc = uwsgi_proc = None
  278. prep_states_ran = False
  279. @classmethod
  280. def setUpClass(cls): # pylint: disable=arguments-differ
  281. """
  282. Set up all the webserver paths. Designed to be run once in a
  283. setUpClass function.
  284. """
  285. super().setUpClass()
  286. cls.root_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  287. cls.config_dir = os.path.join(cls.root_dir, "config")
  288. cls.nginx_conf = os.path.join(cls.config_dir, "nginx.conf")
  289. cls.uwsgi_conf = os.path.join(cls.config_dir, "uwsgi.yml")
  290. cls.git_dir = os.path.join(cls.root_dir, "git")
  291. cls.repo_dir = os.path.join(cls.git_dir, "repos")
  292. cls.venv_dir = os.path.join(cls.root_dir, "venv")
  293. cls.uwsgi_bin = os.path.join(cls.venv_dir, "bin", "uwsgi")
  294. cls.nginx_port = cls.uwsgi_port = get_unused_localhost_port(cached_seconds=120)
  295. cls.uwsgi_port = get_unused_localhost_port(cached_seconds=120)
  296. cls.url = "http://127.0.0.1:{port}/repo.git".format(port=cls.nginx_port)
  297. cls.url_extra_repo = "http://127.0.0.1:{port}/extra_repo.git".format(
  298. port=cls.nginx_port
  299. )
  300. cls.ext_opts = {"url": cls.url, "url_extra_repo": cls.url_extra_repo}
  301. # Add auth params if present (if so this will trigger the spawned
  302. # server to turn on HTTP basic auth).
  303. for credential_param in ("user", "password"):
  304. if hasattr(cls, credential_param):
  305. cls.ext_opts[credential_param] = getattr(cls, credential_param)
  306. auth_enabled = hasattr(cls, "username") and hasattr(cls, "password")
  307. pillar = {
  308. "git_pillar": {
  309. "config_dir": cls.config_dir,
  310. "git_dir": cls.git_dir,
  311. "venv_dir": cls.venv_dir,
  312. "root_dir": cls.root_dir,
  313. "nginx_port": cls.nginx_port,
  314. "uwsgi_port": cls.uwsgi_port,
  315. "auth_enabled": auth_enabled,
  316. }
  317. }
  318. # Different libexec dir for git backend on Debian and FreeBSD-based systems
  319. if salt.utils.platform.is_freebsd():
  320. git_core = "/usr/local/libexec/git-core"
  321. else:
  322. git_core = "/usr/libexec/git-core"
  323. if not os.path.exists(git_core):
  324. git_core = "/usr/lib/git-core"
  325. if not os.path.exists(git_core):
  326. cls.tearDownClass()
  327. raise AssertionError(
  328. "{} not found. Either git is not installed, or the test "
  329. "class needs to be updated.".format(git_core)
  330. )
  331. pillar["git_pillar"]["git-http-backend"] = os.path.join(
  332. git_core, "git-http-backend"
  333. )
  334. try:
  335. if cls.prep_states_ran is False:
  336. ret = cls.cls_run_function(
  337. "state.apply", mods="git_pillar.http", pillar=pillar
  338. )
  339. assert next(iter(ret.values()))["result"] is True
  340. cls.prep_states_ran = True
  341. log.info("%s: States applied", cls.__name__)
  342. if cls.uwsgi_proc is not None:
  343. if not psutil.pid_exists(cls.uwsgi_proc.pid):
  344. log.warning(
  345. "%s: uWsgi started but appears to be dead now. Will try to restart it.",
  346. cls.__name__,
  347. )
  348. cls.uwsgi_proc = None
  349. if cls.uwsgi_proc is None:
  350. cls.uwsgi_proc = start_daemon(
  351. cls.uwsgi_bin,
  352. UwsgiDaemon,
  353. config_dir=cls.config_dir,
  354. check_port=cls.uwsgi_port,
  355. )
  356. log.info("%s: %s started", cls.__name__, cls.uwsgi_bin)
  357. if cls.nginx_proc is not None:
  358. if not psutil.pid_exists(cls.nginx_proc.pid):
  359. log.warning(
  360. "%s: nginx started but appears to be dead now. Will try to restart it.",
  361. cls.__name__,
  362. )
  363. cls.nginx_proc = None
  364. if cls.nginx_proc is None:
  365. cls.nginx_proc = start_daemon(
  366. "nginx",
  367. NginxDaemon,
  368. config_dir=cls.config_dir,
  369. check_port=cls.nginx_port,
  370. )
  371. log.info("%s: nginx started", cls.__name__)
  372. except AssertionError:
  373. cls.tearDownClass()
  374. raise
  375. @classmethod
  376. def tearDownClass(cls):
  377. if cls.nginx_proc is not None:
  378. log.info(
  379. "[%s] Stopping %s",
  380. cls.nginx_proc.get_log_prefix(),
  381. cls.nginx_proc.__class__.__name__,
  382. )
  383. terminate_process(cls.nginx_proc.pid, kill_children=True, slow_stop=True)
  384. log.info(
  385. "[%s] %s stopped",
  386. cls.nginx_proc.get_log_prefix(),
  387. cls.nginx_proc.__class__.__name__,
  388. )
  389. cls.nginx_proc = None
  390. if cls.uwsgi_proc is not None:
  391. log.info(
  392. "[%s] Stopping %s",
  393. cls.uwsgi_proc.get_log_prefix(),
  394. cls.uwsgi_proc.__class__.__name__,
  395. )
  396. terminate_process(cls.uwsgi_proc.pid, kill_children=True, slow_stop=True)
  397. log.info(
  398. "[%s] %s stopped",
  399. cls.uwsgi_proc.get_log_prefix(),
  400. cls.uwsgi_proc.__class__.__name__,
  401. )
  402. cls.uwsgi_proc = None
  403. shutil.rmtree(cls.root_dir, ignore_errors=True)
  404. cls.prep_states_ran = False
  405. super().tearDownClass()
  406. class GitTestBase(ModuleCase):
  407. """
  408. Base class for all gitfs/git_pillar tests. Must be subclassed and paired
  409. with either SSHDMixin or WebserverMixin to provide the server.
  410. """
  411. maxDiff = None
  412. git_opts = '-c user.name="Foo Bar" -c user.email=foo@bar.com'
  413. ext_opts = {}
  414. def make_repo(self, root_dir, user="root"):
  415. raise NotImplementedError()
  416. class GitFSTestBase(GitTestBase, LoaderModuleMockMixin):
  417. """
  418. Base class for all gitfs tests
  419. """
  420. @requires_system_grains
  421. def setup_loader_modules(self, grains): # pylint: disable=W0221
  422. return {gitfs: {"__opts__": copy.copy(_OPTS), "__grains__": grains}}
  423. def make_repo(self, root_dir, user="root"):
  424. raise NotImplementedError()
  425. class GitPillarTestBase(GitTestBase, LoaderModuleMockMixin):
  426. """
  427. Base class for all git_pillar tests
  428. """
  429. bare_repo = bare_repo_backup = bare_extra_repo = bare_extra_repo_backup = None
  430. admin_repo = admin_repo_backup = admin_extra_repo = admin_extra_repo_backup = None
  431. @requires_system_grains
  432. def setup_loader_modules(self, grains): # pylint: disable=W0221
  433. return {git_pillar: {"__opts__": copy.copy(_OPTS), "__grains__": grains}}
  434. def get_pillar(self, ext_pillar_conf):
  435. """
  436. Run git_pillar with the specified configuration
  437. """
  438. cachedir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  439. self.addCleanup(shutil.rmtree, cachedir, ignore_errors=True)
  440. ext_pillar_opts = {"optimization_order": [0, 1, 2]}
  441. ext_pillar_opts.update(
  442. salt.utils.yaml.safe_load(
  443. ext_pillar_conf.format(
  444. cachedir=cachedir,
  445. extmods=os.path.join(cachedir, "extmods"),
  446. **self.ext_opts
  447. )
  448. )
  449. )
  450. with patch.dict(git_pillar.__opts__, ext_pillar_opts):
  451. return git_pillar.ext_pillar(
  452. "minion", {}, *ext_pillar_opts["ext_pillar"][0]["git"]
  453. )
  454. def make_repo(self, root_dir, user="root"):
  455. log.info("Creating test Git repo....")
  456. self.bare_repo = os.path.join(root_dir, "repo.git")
  457. self.bare_repo_backup = "{}.backup".format(self.bare_repo)
  458. self.admin_repo = os.path.join(root_dir, "admin")
  459. self.admin_repo_backup = "{}.backup".format(self.admin_repo)
  460. for dirname in (self.bare_repo, self.admin_repo):
  461. shutil.rmtree(dirname, ignore_errors=True)
  462. if os.path.exists(self.bare_repo_backup) and os.path.exists(
  463. self.admin_repo_backup
  464. ):
  465. shutil.copytree(self.bare_repo_backup, self.bare_repo)
  466. shutil.copytree(self.admin_repo_backup, self.admin_repo)
  467. return
  468. # Create bare repo
  469. self.run_function("git.init", [self.bare_repo], user=user, bare=True)
  470. # Clone bare repo
  471. self.run_function("git.clone", [self.admin_repo], url=self.bare_repo, user=user)
  472. def _push(branch, message):
  473. self.run_function("git.add", [self.admin_repo, "."], user=user)
  474. self.run_function(
  475. "git.commit",
  476. [self.admin_repo, message],
  477. user=user,
  478. git_opts=self.git_opts,
  479. )
  480. self.run_function(
  481. "git.push", [self.admin_repo], remote="origin", ref=branch, user=user,
  482. )
  483. with salt.utils.files.fopen(
  484. os.path.join(self.admin_repo, "top.sls"), "w"
  485. ) as fp_:
  486. fp_.write(
  487. textwrap.dedent(
  488. """\
  489. base:
  490. '*':
  491. - foo
  492. """
  493. )
  494. )
  495. with salt.utils.files.fopen(
  496. os.path.join(self.admin_repo, "foo.sls"), "w"
  497. ) as fp_:
  498. fp_.write(
  499. textwrap.dedent(
  500. """\
  501. branch: master
  502. mylist:
  503. - master
  504. mydict:
  505. master: True
  506. nested_list:
  507. - master
  508. nested_dict:
  509. master: True
  510. """
  511. )
  512. )
  513. # Add another file to be referenced using git_pillar_includes
  514. with salt.utils.files.fopen(
  515. os.path.join(self.admin_repo, "bar.sls"), "w"
  516. ) as fp_:
  517. fp_.write("included_pillar: True\n")
  518. # Add another file in subdir
  519. os.mkdir(os.path.join(self.admin_repo, "subdir"))
  520. with salt.utils.files.fopen(
  521. os.path.join(self.admin_repo, "subdir", "bar.sls"), "w"
  522. ) as fp_:
  523. fp_.write("from_subdir: True\n")
  524. _push("master", "initial commit")
  525. # Do the same with different values for "dev" branch
  526. self.run_function("git.checkout", [self.admin_repo], user=user, opts="-b dev")
  527. # The bar.sls shouldn't be in any branch but master
  528. self.run_function("git.rm", [self.admin_repo, "bar.sls"], user=user)
  529. with salt.utils.files.fopen(
  530. os.path.join(self.admin_repo, "top.sls"), "w"
  531. ) as fp_:
  532. fp_.write(
  533. textwrap.dedent(
  534. """\
  535. dev:
  536. '*':
  537. - foo
  538. """
  539. )
  540. )
  541. with salt.utils.files.fopen(
  542. os.path.join(self.admin_repo, "foo.sls"), "w"
  543. ) as fp_:
  544. fp_.write(
  545. textwrap.dedent(
  546. """\
  547. branch: dev
  548. mylist:
  549. - dev
  550. mydict:
  551. dev: True
  552. nested_list:
  553. - dev
  554. nested_dict:
  555. dev: True
  556. """
  557. )
  558. )
  559. _push("dev", "add dev branch")
  560. # Create just a top file in a separate repo, to be mapped to the base
  561. # env and referenced using git_pillar_includes
  562. self.run_function(
  563. "git.checkout", [self.admin_repo], user=user, opts="-b top_only"
  564. )
  565. # The top.sls should be the only file in this branch
  566. self.run_function(
  567. "git.rm",
  568. [self.admin_repo, "foo.sls", os.path.join("subdir", "bar.sls")],
  569. user=user,
  570. )
  571. with salt.utils.files.fopen(
  572. os.path.join(self.admin_repo, "top.sls"), "w"
  573. ) as fp_:
  574. fp_.write(
  575. textwrap.dedent(
  576. """\
  577. base:
  578. '*':
  579. - bar
  580. """
  581. )
  582. )
  583. _push("top_only", "add top_only branch")
  584. # Create just another top file in a separate repo, to be mapped to the base
  585. # env and including mounted.bar
  586. self.run_function(
  587. "git.checkout", [self.admin_repo], user=user, opts="-b top_mounted"
  588. )
  589. # The top.sls should be the only file in this branch
  590. with salt.utils.files.fopen(
  591. os.path.join(self.admin_repo, "top.sls"), "w"
  592. ) as fp_:
  593. fp_.write(
  594. textwrap.dedent(
  595. """\
  596. base:
  597. '*':
  598. - mounted.bar
  599. """
  600. )
  601. )
  602. _push("top_mounted", "add top_mounted branch")
  603. shutil.copytree(self.bare_repo, self.bare_repo_backup)
  604. shutil.copytree(self.admin_repo, self.admin_repo_backup)
  605. log.info("Test Git repo created.")
  606. def make_extra_repo(self, root_dir, user="root"):
  607. log.info("Creating extra test Git repo....")
  608. self.bare_extra_repo = os.path.join(root_dir, "extra_repo.git")
  609. self.bare_extra_repo_backup = "{}.backup".format(self.bare_extra_repo)
  610. self.admin_extra_repo = os.path.join(root_dir, "admin_extra")
  611. self.admin_extra_repo_backup = "{}.backup".format(self.admin_extra_repo)
  612. for dirname in (self.bare_extra_repo, self.admin_extra_repo):
  613. shutil.rmtree(dirname, ignore_errors=True)
  614. if os.path.exists(self.bare_extra_repo_backup) and os.path.exists(
  615. self.admin_extra_repo_backup
  616. ):
  617. shutil.copytree(self.bare_extra_repo_backup, self.bare_extra_repo)
  618. shutil.copytree(self.admin_extra_repo_backup, self.admin_extra_repo)
  619. return
  620. # Create bare extra repo
  621. self.run_function("git.init", [self.bare_extra_repo], user=user, bare=True)
  622. # Clone bare repo
  623. self.run_function(
  624. "git.clone", [self.admin_extra_repo], url=self.bare_extra_repo, user=user
  625. )
  626. def _push(branch, message):
  627. self.run_function("git.add", [self.admin_extra_repo, "."], user=user)
  628. self.run_function(
  629. "git.commit",
  630. [self.admin_extra_repo, message],
  631. user=user,
  632. git_opts=self.git_opts,
  633. )
  634. self.run_function(
  635. "git.push",
  636. [self.admin_extra_repo],
  637. remote="origin",
  638. ref=branch,
  639. user=user,
  640. )
  641. with salt.utils.files.fopen(
  642. os.path.join(self.admin_extra_repo, "top.sls"), "w"
  643. ) as fp_:
  644. fp_.write(
  645. textwrap.dedent(
  646. """\
  647. "{{saltenv}}":
  648. '*':
  649. - motd
  650. - nowhere.foo
  651. """
  652. )
  653. )
  654. with salt.utils.files.fopen(
  655. os.path.join(self.admin_extra_repo, "motd.sls"), "w"
  656. ) as fp_:
  657. fp_.write(
  658. textwrap.dedent(
  659. """\
  660. motd: The force will be with you. Always.
  661. """
  662. )
  663. )
  664. _push("master", "initial commit")
  665. shutil.copytree(self.bare_extra_repo, self.bare_extra_repo_backup)
  666. shutil.copytree(self.admin_extra_repo, self.admin_extra_repo_backup)
  667. log.info("Extra test Git repo created.")
  668. @classmethod
  669. def tearDownClass(cls):
  670. super().tearDownClass()
  671. for dirname in (
  672. cls.admin_repo,
  673. cls.admin_repo_backup,
  674. cls.admin_extra_repo,
  675. cls.admin_extra_repo_backup,
  676. cls.bare_repo,
  677. cls.bare_repo_backup,
  678. cls.bare_extra_repo,
  679. cls.bare_extra_repo_backup,
  680. ):
  681. if dirname is not None:
  682. shutil.rmtree(dirname, ignore_errors=True)
  683. class GitPillarSSHTestBase(GitPillarTestBase, SSHDMixin):
  684. """
  685. Base class for GitPython and Pygit2 SSH tests
  686. """
  687. id_rsa_nopass = id_rsa_withpass = None
  688. git_ssh = "/tmp/git_ssh"
  689. def setUp(self):
  690. """
  691. Create the SSH server and user, and create the git repo
  692. """
  693. log.info("%s.setUp() started...", self.__class__.__name__)
  694. super().setUp()
  695. root_dir = os.path.expanduser("~{}".format(self.username))
  696. if root_dir.startswith("~"):
  697. raise AssertionError(
  698. "Unable to resolve homedir for user '{}'".format(self.username)
  699. )
  700. self.make_repo(root_dir, user=self.username)
  701. self.make_extra_repo(root_dir, user=self.username)
  702. log.info("%s.setUp() complete.", self.__class__.__name__)
  703. def get_pillar(self, ext_pillar_conf):
  704. """
  705. Wrap the parent class' get_pillar() func in logic that temporarily
  706. changes the GIT_SSH to use our custom script, ensuring that the
  707. passphraselsess key is used to auth without needing to modify the root
  708. user's ssh config file.
  709. """
  710. with patched_environ(GIT_SSH=self.git_ssh):
  711. return super().get_pillar(ext_pillar_conf)
  712. class GitPillarHTTPTestBase(GitPillarTestBase, WebserverMixin):
  713. """
  714. Base class for GitPython and Pygit2 HTTP tests
  715. """
  716. def setUp(self):
  717. """
  718. Create and start the webserver, and create the git repo
  719. """
  720. log.info("%s.setUp() started...", self.__class__.__name__)
  721. super().setUp()
  722. self.make_repo(self.repo_dir)
  723. self.make_extra_repo(self.repo_dir)
  724. log.info("%s.setUp() complete", self.__class__.__name__)