conftest.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, unicode_literals, print_function
  3. # Import python libraries
  4. import distutils.spawn
  5. import logging
  6. import os.path
  7. import shutil
  8. import sys
  9. import tempfile
  10. import textwrap
  11. # Import Salt Libraries
  12. import salt.client
  13. import salt.config
  14. import salt.utils.yaml as yaml
  15. # Import pytest libraries
  16. import pytest
  17. from pytestsalt.utils import SaltDaemonScriptBase, start_daemon, get_unused_localhost_port
  18. # Import Pepper libraries
  19. import pepper
  20. import pepper.script
  21. DEFAULT_MASTER_ID = 'pytest-salt-master'
  22. DEFAULT_MINION_ID = 'pytest-salt-minion'
  23. log = logging.getLogger(__name__)
  24. @pytest.fixture(scope='session')
  25. def install_sshd_server():
  26. if distutils.spawn.find_executable('sshd'):
  27. return
  28. __opts__ = salt.config.minion_config('tests/minion.conf')
  29. __opts__['file_client'] = 'local'
  30. caller = salt.client.Caller(mopts=__opts__)
  31. caller.cmd('pkg.install', 'openssh-server')
  32. class SaltApi(SaltDaemonScriptBase):
  33. '''
  34. Class which runs the salt-api daemon
  35. '''
  36. def get_script_args(self):
  37. return ['-l', 'quiet']
  38. def get_check_ports(self):
  39. if 'rest_cherrypy' in self.config:
  40. return [self.config['rest_cherrypy']['port']]
  41. if 'rest_tornado' in self.config:
  42. return [self.config['rest_tornado']['port']]
  43. @pytest.fixture(scope='session')
  44. def salt_api_port():
  45. '''
  46. Returns an unused localhost port for the api port
  47. '''
  48. return get_unused_localhost_port()
  49. @pytest.fixture(scope='session')
  50. def pepperconfig(salt_api_port):
  51. config = textwrap.dedent('''
  52. [main]
  53. SALTAPI_URL=http://localhost:{0}/
  54. SALTAPI_USER=pepper
  55. SALTAPI_PASS=pepper
  56. SALTAPI_EAUTH=sharedsecret
  57. [pepper]
  58. SALTAPI_URL=http://localhost:{0}/
  59. SALTAPI_USER=pepper
  60. SALTAPI_PASS=pepper
  61. SALTAPI_EAUTH=sharedsecret
  62. [baduser]
  63. SALTAPI_URL=http://localhost:{0}/
  64. SALTAPI_USER=saltdev
  65. SALTAPI_PASS=saltdev
  66. SALTAPI_EAUTH=pam
  67. [badapi]
  68. SALTAPI_URL=git://localhost:{0}/
  69. [noapi]
  70. SALTAPI_USER=pepper
  71. SALTAPI_PASS=pepper
  72. SALTAPI_EAUTH=sharedsecret
  73. [noopts]
  74. SALTAPI_URL=http://localhost:{0}/
  75. '''.format(salt_api_port))
  76. with open('tests/.pepperrc', 'w') as pepper_file:
  77. print(config, file=pepper_file)
  78. yield
  79. os.remove('tests/.pepperrc')
  80. @pytest.fixture
  81. def pepper_client(session_salt_api, salt_api_port):
  82. client = pepper.Pepper('http://localhost:{0}'.format(salt_api_port))
  83. client.login('pepper', 'pepper', 'sharedsecret')
  84. return client
  85. @pytest.fixture
  86. def tokfile():
  87. tokdir = tempfile.mkdtemp()
  88. yield os.path.join(tokdir, 'peppertok.json')
  89. shutil.rmtree(tokdir)
  90. @pytest.fixture
  91. def output_file():
  92. '''
  93. Returns the path to the salt master configuration file
  94. '''
  95. out_dir = tempfile.mkdtemp()
  96. yield os.path.join(out_dir, 'output')
  97. shutil.rmtree(out_dir)
  98. @pytest.fixture(params=['/run', '/login'])
  99. def pepper_cli(request, session_salt_api, salt_api_port, output_file, install_sshd_server, session_sshd_server):
  100. '''
  101. Wrapper to invoke Pepper with common params and inside an empty env
  102. '''
  103. if request.config.getoption('--salt-api-backend') == 'rest_tornado' and request.param == '/run':
  104. pytest.xfail("rest_tornado does not support /run endpoint until next release")
  105. def_args = [
  106. '--out=json',
  107. '--output-file={0}'.format(output_file),
  108. '-c', 'tests/.pepperrc',
  109. ]
  110. if request.param == '/run':
  111. def_args = ['--run-uri'] + def_args
  112. def _run_pepper_cli(*args, **kwargs):
  113. sys.argv = ['pepper', '-p', kwargs.pop('profile', 'main')] + def_args + list(args)
  114. exitcode = pepper.script.Pepper()()
  115. try:
  116. with open(output_file, 'r') as result:
  117. try:
  118. return yaml.load(result)
  119. except yaml.parser.ParserError:
  120. result.seek(0)
  121. return [yaml.load('{0}}}'.format(ret).strip('"')) for ret in result.read().split('}"\n') if ret]
  122. except Exception as exc:
  123. log.error('ExitCode %s: %s', exitcode, exc)
  124. return exitcode
  125. return _run_pepper_cli
  126. @pytest.fixture(scope='session')
  127. def session_master_config_overrides(request, salt_api_port, salt_api_backend):
  128. return {
  129. salt_api_backend: {
  130. 'port': salt_api_port,
  131. 'disable_ssl': True,
  132. },
  133. 'external_auth': {
  134. 'sharedsecret': {
  135. 'pepper': [
  136. '.*',
  137. '@jobs',
  138. '@wheel',
  139. '@runner',
  140. ],
  141. },
  142. },
  143. 'sharedsecret': 'pepper',
  144. 'token_expire': 94670856,
  145. 'ignore_host_keys': True,
  146. 'ssh_wipe': True,
  147. }
  148. @pytest.fixture(scope='session')
  149. def session_api_log_prefix(master_id):
  150. return 'salt-api/{0}'.format(master_id)
  151. @pytest.fixture(scope='session')
  152. def cli_api_script_name():
  153. '''
  154. Return the CLI script basename
  155. '''
  156. return 'salt-api'
  157. @pytest.yield_fixture(scope='session')
  158. def session_salt_api_before_start():
  159. '''
  160. This fixture should be overridden if you need to do
  161. some preparation and clean up work before starting
  162. the salt-api and after ending it.
  163. '''
  164. # Prep routines go here
  165. # Start the salt-api
  166. yield
  167. # Clean routines go here
  168. @pytest.yield_fixture(scope='session')
  169. def session_salt_api_after_start(session_salt_api):
  170. '''
  171. This fixture should be overridden if you need to do
  172. some preparation and clean up work after starting
  173. the salt-api and before ending it.
  174. '''
  175. # Prep routines go here
  176. # Resume test execution
  177. yield
  178. # Clean routines go here
  179. @pytest.fixture(scope='session')
  180. def _salt_fail_hard(request, salt_fail_hard):
  181. '''
  182. Return the salt fail hard value
  183. '''
  184. fail_hard = request.config.getoption('salt_fail_hard')
  185. if fail_hard is not None:
  186. # We were passed --salt-fail-hard as a CLI option
  187. return fail_hard
  188. # The salt fail hard was not passed as a CLI option
  189. fail_hard = request.config.getini('salt_fail_hard')
  190. if fail_hard != []:
  191. # We were passed salt_fail_hard as a INI option
  192. return fail_hard
  193. return salt_fail_hard
  194. @pytest.fixture(scope='session')
  195. def salt_api_backend(request):
  196. '''
  197. Return the salt-api backend (cherrypy or tornado)
  198. '''
  199. backend = request.config.getoption('--salt-api-backend')
  200. if backend is not None:
  201. return backend
  202. backend = request.config.getini('salt_api_backend')
  203. if backend is not None:
  204. return backend
  205. return 'rest_cherrypy'
  206. @pytest.fixture(scope='session')
  207. def master_id(salt_master_id_counter):
  208. '''
  209. Returns the master id
  210. '''
  211. return DEFAULT_MASTER_ID + '-{0}'.format(salt_master_id_counter())
  212. @pytest.fixture(scope='session')
  213. def minion_id(salt_minion_id_counter):
  214. '''
  215. Returns the minion id
  216. '''
  217. return DEFAULT_MINION_ID + '-{0}'.format(salt_minion_id_counter())
  218. @pytest.fixture(scope='session')
  219. def session_salt_api(request,
  220. session_salt_minion,
  221. session_master_id,
  222. session_master_config,
  223. session_salt_api_before_start, # pylint: disable=unused-argument
  224. session_api_log_prefix,
  225. cli_api_script_name,
  226. log_server,
  227. _cli_bin_dir,
  228. session_conf_dir):
  229. '''
  230. Returns a running salt-api
  231. '''
  232. return start_daemon(request,
  233. daemon_name='salt-api',
  234. daemon_id=session_master_id,
  235. daemon_log_prefix=session_api_log_prefix,
  236. daemon_cli_script_name=cli_api_script_name,
  237. daemon_config=session_master_config,
  238. daemon_config_dir=session_conf_dir,
  239. daemon_class=SaltApi,
  240. bin_dir_path=_cli_bin_dir,
  241. start_timeout=30)
  242. @pytest.fixture(scope='session')
  243. def session_sshd_config_lines(session_sshd_port):
  244. '''
  245. Return a list of lines which will make the sshd_config file
  246. '''
  247. return [
  248. 'Port {0}'.format(session_sshd_port),
  249. 'ListenAddress 127.0.0.1',
  250. 'Protocol 2',
  251. 'UsePrivilegeSeparation yes',
  252. '# Turn strict modes off so that we can operate in /tmp',
  253. 'StrictModes no',
  254. '# Logging',
  255. 'SyslogFacility AUTH',
  256. 'LogLevel INFO',
  257. '# Authentication:',
  258. 'LoginGraceTime 120',
  259. 'PermitRootLogin without-password',
  260. 'StrictModes yes',
  261. 'PubkeyAuthentication yes',
  262. '#AuthorizedKeysFile %h/.ssh/authorized_keys',
  263. '#AuthorizedKeysFile key_test.pub',
  264. '# Don\'t read the user\'s ~/.rhosts and ~/.shosts files',
  265. 'IgnoreRhosts yes',
  266. '# similar for protocol version 2',
  267. 'HostbasedAuthentication no',
  268. '#IgnoreUserKnownHosts yes',
  269. '# To enable empty passwords, change to yes (NOT RECOMMENDED)',
  270. 'PermitEmptyPasswords no',
  271. '# Change to yes to enable challenge-response passwords (beware issues with',
  272. '# some PAM modules and threads)',
  273. 'ChallengeResponseAuthentication no',
  274. '# Change to no to disable tunnelled clear text passwords',
  275. 'PasswordAuthentication no',
  276. 'X11Forwarding no',
  277. 'X11DisplayOffset 10',
  278. 'PrintMotd no',
  279. 'PrintLastLog yes',
  280. 'TCPKeepAlive yes',
  281. '#UseLogin no',
  282. 'AcceptEnv LANG LC_*',
  283. 'Subsystem sftp /usr/lib/openssh/sftp-server',
  284. '#UsePAM yes',
  285. ]
  286. def pytest_addoption(parser):
  287. parser.addoption(
  288. '--salt-api-backend',
  289. action='store',
  290. default='rest_cherrypy',
  291. help='which backend to use for salt-api, must be one of rest_cherrypy or rest_tornado',
  292. )