test_archive.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  4. tests.unit.modules.archive_test
  5. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. '''
  7. # Import Python libs
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. import os.path
  10. # Import Salt Testing libs
  11. from tests.support.mixins import LoaderModuleMockMixin
  12. from tests.support.unit import skipIf, TestCase
  13. from tests.support.mock import MagicMock, patch
  14. # Import salt libs
  15. import salt.modules.archive as archive
  16. import salt.utils.path
  17. from salt.exceptions import CommandNotFoundError
  18. # Import 3rd-party libs
  19. from salt.ext.six.moves import zip
  20. class ZipFileMock(MagicMock):
  21. def __init__(self, files=None, **kwargs): # pylint: disable=W0231
  22. if files is None:
  23. files = ['salt']
  24. MagicMock.__init__(self, **kwargs)
  25. self._files = files
  26. self.external_attr = 0o0777 << 16
  27. def namelist(self):
  28. return self._files
  29. class ArchiveTestCase(TestCase, LoaderModuleMockMixin):
  30. def setup_loader_modules(self):
  31. return {archive: {'__grains__': {'id': 0}}}
  32. def test_tar(self):
  33. with patch('glob.glob', lambda pathname: [pathname]):
  34. with patch('salt.utils.path.which', lambda exe: exe):
  35. mock = MagicMock(return_value='salt')
  36. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  37. ret = archive.tar(
  38. '-zcvf', 'foo.tar',
  39. ['/tmp/something-to-compress-1',
  40. '/tmp/something-to-compress-2'],
  41. template=None
  42. )
  43. self.assertEqual(['salt'], ret)
  44. mock.assert_called_once_with(
  45. ['tar', '-zcvf', 'foo.tar', '/tmp/something-to-compress-1',
  46. '/tmp/something-to-compress-2'],
  47. runas=None, python_shell=False, template=None, cwd=None
  48. )
  49. mock = MagicMock(return_value='salt')
  50. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  51. ret = archive.tar(
  52. '-zcvf', 'foo.tar',
  53. '/tmp/something-to-compress-1,/tmp/something-to-compress-2',
  54. template=None
  55. )
  56. self.assertEqual(['salt'], ret)
  57. mock.assert_called_once_with(
  58. ['tar', '-zcvf', 'foo.tar', '/tmp/something-to-compress-1',
  59. '/tmp/something-to-compress-2'],
  60. runas=None, python_shell=False, template=None, cwd=None
  61. )
  62. def test_tar_raises_exception_if_not_found(self):
  63. with patch('salt.utils.path.which', lambda exe: None):
  64. mock = MagicMock(return_value='salt')
  65. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  66. self.assertRaises(
  67. CommandNotFoundError,
  68. archive.tar,
  69. 'zxvf',
  70. 'foo.tar',
  71. '/tmp/something-to-compress'
  72. )
  73. self.assertFalse(mock.called)
  74. def test_gzip(self):
  75. mock = MagicMock(return_value='salt')
  76. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  77. with patch('salt.utils.path.which', lambda exe: exe):
  78. ret = archive.gzip('/tmp/something-to-compress')
  79. self.assertEqual(['salt'], ret)
  80. mock.assert_called_once_with(
  81. ['gzip', '/tmp/something-to-compress'],
  82. runas=None, python_shell=False, template=None
  83. )
  84. def test_gzip_raises_exception_if_not_found(self):
  85. mock = MagicMock(return_value='salt')
  86. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  87. with patch('salt.utils.path.which', lambda exe: None):
  88. self.assertRaises(
  89. CommandNotFoundError,
  90. archive.gzip, '/tmp/something-to-compress'
  91. )
  92. self.assertFalse(mock.called)
  93. def test_gunzip(self):
  94. mock = MagicMock(return_value='salt')
  95. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  96. with patch('salt.utils.path.which', lambda exe: exe):
  97. ret = archive.gunzip('/tmp/something-to-decompress.tar.gz')
  98. self.assertEqual(['salt'], ret)
  99. mock.assert_called_once_with(
  100. ['gunzip', '/tmp/something-to-decompress.tar.gz'],
  101. runas=None, python_shell=False, template=None
  102. )
  103. def test_gunzip_raises_exception_if_not_found(self):
  104. mock = MagicMock(return_value='salt')
  105. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  106. with patch('salt.utils.path.which', lambda exe: None):
  107. self.assertRaises(
  108. CommandNotFoundError,
  109. archive.gunzip,
  110. '/tmp/something-to-decompress.tar.gz'
  111. )
  112. self.assertFalse(mock.called)
  113. def test_cmd_zip(self):
  114. with patch('glob.glob', lambda pathname: [pathname]):
  115. with patch('salt.utils.path.which', lambda exe: exe):
  116. mock = MagicMock(return_value='salt')
  117. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  118. ret = archive.cmd_zip(
  119. '/tmp/salt.{{grains.id}}.zip',
  120. '/tmp/tmpePe8yO,/tmp/tmpLeSw1A',
  121. template='jinja'
  122. )
  123. self.assertEqual(['salt'], ret)
  124. mock.assert_called_once_with(
  125. ['zip', '-r', '/tmp/salt.{{grains.id}}.zip',
  126. '/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  127. runas=None, python_shell=False, template='jinja', cwd=None
  128. )
  129. mock = MagicMock(return_value='salt')
  130. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  131. ret = archive.cmd_zip(
  132. '/tmp/salt.{{grains.id}}.zip',
  133. ['/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  134. template='jinja'
  135. )
  136. self.assertEqual(['salt'], ret)
  137. mock.assert_called_once_with(
  138. ['zip', '-r', '/tmp/salt.{{grains.id}}.zip',
  139. '/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  140. runas=None, python_shell=False, template='jinja', cwd=None
  141. )
  142. def test_zip(self):
  143. with patch('glob.glob', lambda pathname: [pathname]):
  144. with patch.multiple(os.path, **{'isdir': MagicMock(return_value=False),
  145. 'exists': MagicMock(return_value=True)}):
  146. with patch('zipfile.ZipFile', MagicMock()):
  147. ret = archive.zip_(
  148. '/tmp/salt.{{grains.id}}.zip',
  149. '/tmp/tmpePe8yO,/tmp/tmpLeSw1A',
  150. template='jinja'
  151. )
  152. expected = [os.path.join('tmp', 'tmpePe8yO'),
  153. os.path.join('tmp', 'tmpLeSw1A')]
  154. self.assertEqual(expected, ret)
  155. def test_zip_raises_exception_if_not_found(self):
  156. mock = MagicMock(return_value='salt')
  157. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  158. with patch('salt.utils.path.which', lambda exe: None):
  159. self.assertRaises(
  160. CommandNotFoundError,
  161. archive.cmd_zip,
  162. '/tmp/salt.{{grains.id}}.zip',
  163. '/tmp/tmpePe8yO,/tmp/tmpLeSw1A',
  164. template='jinja',
  165. )
  166. self.assertFalse(mock.called)
  167. def test_cmd_unzip(self):
  168. def _get_mock():
  169. '''
  170. Create a new MagicMock for each scenario in this test, so that
  171. assert_called_once_with doesn't complain that the same mock object
  172. is called more than once.
  173. '''
  174. return MagicMock(return_value={'stdout': 'salt',
  175. 'stderr': '',
  176. 'pid': 12345,
  177. 'retcode': 0})
  178. with patch('salt.utils.path.which', lambda exe: exe):
  179. mock = _get_mock()
  180. with patch.dict(archive.__salt__, {'cmd.run_all': mock}):
  181. ret = archive.cmd_unzip(
  182. '/tmp/salt.{{grains.id}}.zip',
  183. '/tmp/dest',
  184. excludes='/tmp/tmpePe8yO,/tmp/tmpLeSw1A',
  185. template='jinja'
  186. )
  187. self.assertEqual(['salt'], ret)
  188. mock.assert_called_once_with(
  189. ['unzip', '/tmp/salt.{{grains.id}}.zip', '-d', '/tmp/dest',
  190. '-x', '/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  191. output_loglevel='debug',
  192. python_shell=False,
  193. redirect_stderr=True,
  194. runas=None,
  195. template='jinja'
  196. )
  197. mock = _get_mock()
  198. with patch.dict(archive.__salt__, {'cmd.run_all': mock}):
  199. ret = archive.cmd_unzip(
  200. '/tmp/salt.{{grains.id}}.zip',
  201. '/tmp/dest',
  202. excludes=['/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  203. template='jinja'
  204. )
  205. self.assertEqual(['salt'], ret)
  206. mock.assert_called_once_with(
  207. ['unzip', '/tmp/salt.{{grains.id}}.zip', '-d', '/tmp/dest',
  208. '-x', '/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  209. output_loglevel='debug',
  210. python_shell=False,
  211. redirect_stderr=True,
  212. runas=None,
  213. template='jinja'
  214. )
  215. mock = _get_mock()
  216. with patch.dict(archive.__salt__, {'cmd.run_all': mock}):
  217. ret = archive.cmd_unzip(
  218. '/tmp/salt.{{grains.id}}.zip',
  219. '/tmp/dest',
  220. excludes='/tmp/tmpePe8yO,/tmp/tmpLeSw1A',
  221. template='jinja',
  222. options='-fo'
  223. )
  224. self.assertEqual(['salt'], ret)
  225. mock.assert_called_once_with(
  226. ['unzip', '-fo', '/tmp/salt.{{grains.id}}.zip', '-d',
  227. '/tmp/dest', '-x', '/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  228. output_loglevel='debug',
  229. python_shell=False,
  230. redirect_stderr=True,
  231. runas=None,
  232. template='jinja'
  233. )
  234. mock = _get_mock()
  235. with patch.dict(archive.__salt__, {'cmd.run_all': mock}):
  236. ret = archive.cmd_unzip(
  237. '/tmp/salt.{{grains.id}}.zip',
  238. '/tmp/dest',
  239. excludes=['/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  240. template='jinja',
  241. options='-fo'
  242. )
  243. self.assertEqual(['salt'], ret)
  244. mock.assert_called_once_with(
  245. ['unzip', '-fo', '/tmp/salt.{{grains.id}}.zip', '-d',
  246. '/tmp/dest', '-x', '/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  247. output_loglevel='debug',
  248. python_shell=False,
  249. redirect_stderr=True,
  250. runas=None,
  251. template='jinja'
  252. )
  253. mock = _get_mock()
  254. with patch.dict(archive.__salt__, {'cmd.run_all': mock}):
  255. ret = archive.cmd_unzip(
  256. '/tmp/salt.{{grains.id}}.zip',
  257. '/tmp/dest',
  258. excludes=['/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  259. template='jinja',
  260. options='-fo',
  261. password='asdf',
  262. )
  263. self.assertEqual(['salt'], ret)
  264. mock.assert_called_once_with(
  265. ['unzip', '-P', 'asdf', '-fo', '/tmp/salt.{{grains.id}}.zip',
  266. '-d', '/tmp/dest', '-x', '/tmp/tmpePe8yO', '/tmp/tmpLeSw1A'],
  267. output_loglevel='quiet',
  268. python_shell=False,
  269. redirect_stderr=True,
  270. runas=None,
  271. template='jinja'
  272. )
  273. def test_unzip(self):
  274. mock = ZipFileMock()
  275. with patch('zipfile.ZipFile', mock):
  276. ret = archive.unzip(
  277. '/tmp/salt.{{grains.id}}.zip',
  278. '/tmp/dest',
  279. excludes='/tmp/tmpePe8yO,/tmp/tmpLeSw1A',
  280. template='jinja',
  281. extract_perms=False
  282. )
  283. self.assertEqual(['salt'], ret)
  284. def test_unzip_raises_exception_if_not_found(self):
  285. mock = MagicMock(return_value='salt')
  286. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  287. with patch('salt.utils.path.which', lambda exe: None):
  288. self.assertRaises(
  289. CommandNotFoundError,
  290. archive.cmd_unzip,
  291. '/tmp/salt.{{grains.id}}.zip',
  292. '/tmp/dest',
  293. excludes='/tmp/tmpePe8yO,/tmp/tmpLeSw1A',
  294. template='jinja',
  295. )
  296. self.assertFalse(mock.called)
  297. def test_rar(self):
  298. with patch('glob.glob', lambda pathname: [pathname]):
  299. with patch('salt.utils.path.which', lambda exe: exe):
  300. mock = MagicMock(return_value='salt')
  301. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  302. ret = archive.rar(
  303. '/tmp/rarfile.rar',
  304. '/tmp/sourcefile1,/tmp/sourcefile2'
  305. )
  306. self.assertEqual(['salt'], ret)
  307. mock.assert_called_once_with(
  308. ['rar', 'a', '-idp', '/tmp/rarfile.rar',
  309. '/tmp/sourcefile1', '/tmp/sourcefile2'],
  310. runas=None, python_shell=False, template=None, cwd=None
  311. )
  312. mock = MagicMock(return_value='salt')
  313. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  314. ret = archive.rar(
  315. '/tmp/rarfile.rar',
  316. ['/tmp/sourcefile1', '/tmp/sourcefile2']
  317. )
  318. self.assertEqual(['salt'], ret)
  319. mock.assert_called_once_with(
  320. ['rar', 'a', '-idp', '/tmp/rarfile.rar',
  321. '/tmp/sourcefile1', '/tmp/sourcefile2'],
  322. runas=None, python_shell=False, template=None, cwd=None
  323. )
  324. def test_rar_raises_exception_if_not_found(self):
  325. mock = MagicMock(return_value='salt')
  326. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  327. with patch('salt.utils.path.which', lambda exe: None):
  328. self.assertRaises(
  329. CommandNotFoundError,
  330. archive.rar,
  331. '/tmp/rarfile.rar',
  332. '/tmp/sourcefile1,/tmp/sourcefile2'
  333. )
  334. self.assertFalse(mock.called)
  335. @skipIf(salt.utils.path.which_bin(('unrar', 'rar')) is None, 'unrar not installed')
  336. def test_unrar(self):
  337. with patch('salt.utils.path.which_bin', lambda exe: exe[0] if isinstance(exe, (list, tuple)) else exe):
  338. with patch('salt.utils.path.which', lambda exe: exe[0] if isinstance(exe, (list, tuple)) else exe):
  339. mock = MagicMock(return_value='salt')
  340. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  341. ret = archive.unrar(
  342. '/tmp/rarfile.rar',
  343. '/home/strongbad/',
  344. excludes='file_1,file_2'
  345. )
  346. self.assertEqual(['salt'], ret)
  347. mock.assert_called_once_with(
  348. ['unrar', 'x', '-idp', '/tmp/rarfile.rar',
  349. '-x', 'file_1', '-x', 'file_2', '/home/strongbad/'],
  350. runas=None, python_shell=False, template=None
  351. )
  352. mock = MagicMock(return_value='salt')
  353. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  354. ret = archive.unrar(
  355. '/tmp/rarfile.rar',
  356. '/home/strongbad/',
  357. excludes=['file_1', 'file_2']
  358. )
  359. self.assertEqual(['salt'], ret)
  360. mock.assert_called_once_with(
  361. ['unrar', 'x', '-idp', '/tmp/rarfile.rar',
  362. '-x', 'file_1', '-x', 'file_2', '/home/strongbad/'],
  363. runas=None, python_shell=False, template=None
  364. )
  365. def test_unrar_raises_exception_if_not_found(self):
  366. with patch('salt.utils.path.which_bin', lambda exe: None):
  367. mock = MagicMock(return_value='salt')
  368. with patch.dict(archive.__salt__, {'cmd.run': mock}):
  369. self.assertRaises(
  370. CommandNotFoundError,
  371. archive.unrar,
  372. '/tmp/rarfile.rar',
  373. '/home/strongbad/',
  374. )
  375. self.assertFalse(mock.called)
  376. def test_trim_files(self):
  377. with patch('salt.utils.path.which_bin', lambda exe: exe):
  378. source = 'file.tar.gz'
  379. tmp_dir = 'temp'
  380. files = [
  381. '\n'.join(['file1'] * 200),
  382. '\n'.join(['file2'] * 200),
  383. 'this\nthat\nother',
  384. 'this\nthat\nother',
  385. 'this\nthat\nother'
  386. ]
  387. trim_opt = [
  388. True,
  389. False,
  390. 3,
  391. 1,
  392. 0
  393. ]
  394. trim_100 = (['file1'] * 100)
  395. trim_100.append("List trimmed after 100 files.")
  396. expected = [
  397. trim_100,
  398. ['file2'] * 200,
  399. ['this', 'that', 'other'],
  400. ['this', 'List trimmed after 1 files.'],
  401. ['List trimmed after 0 files.'],
  402. ]
  403. for test_files, test_trim_opts, test_expected in zip(files, trim_opt, expected):
  404. with patch.dict(archive.__salt__, {'cmd.run': MagicMock(return_value=test_files)}):
  405. ret = archive.unrar(source, tmp_dir, trim_output=test_trim_opts)
  406. self.assertEqual(ret, test_expected)