1
0

gitfs.py 28 KB

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