test_pkg.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, print_function, unicode_literals
  3. import os
  4. import pprint
  5. import pytest
  6. import salt.utils.path
  7. import salt.utils.pkg
  8. import salt.utils.platform
  9. from tests.support.case import ModuleCase
  10. from tests.support.helpers import (
  11. destructiveTest,
  12. requires_network,
  13. requires_salt_modules,
  14. requires_salt_states,
  15. requires_system_grains,
  16. skip_if_not_root,
  17. slowTest,
  18. )
  19. from tests.support.mixins import SaltReturnAssertsMixin
  20. from tests.support.unit import skipIf
  21. @pytest.mark.windows_whitelisted
  22. class PkgModuleTest(ModuleCase, SaltReturnAssertsMixin):
  23. """
  24. Validate the pkg module
  25. """
  26. @classmethod
  27. @requires_system_grains
  28. def setUpClass(cls, grains): # pylint: disable=arguments-differ
  29. cls.ctx = {}
  30. cls.pkg = "figlet"
  31. if salt.utils.platform.is_windows():
  32. cls.pkg = "putty"
  33. elif grains["os_family"] == "RedHat":
  34. cls.pkg = "units"
  35. @skip_if_not_root
  36. @requires_salt_modules("pkg.refresh_db")
  37. def setUp(self):
  38. if "refresh" not in self.ctx:
  39. self.run_function("pkg.refresh_db")
  40. self.ctx["refresh"] = True
  41. @requires_salt_modules("pkg.list_pkgs")
  42. @slowTest
  43. def test_list(self):
  44. """
  45. verify that packages are installed
  46. """
  47. ret = self.run_function("pkg.list_pkgs")
  48. self.assertNotEqual(len(ret.keys()), 0)
  49. @requires_salt_modules("pkg.version_cmp")
  50. @requires_system_grains
  51. @slowTest
  52. def test_version_cmp(self, grains):
  53. """
  54. test package version comparison on supported platforms
  55. """
  56. func = "pkg.version_cmp"
  57. if grains["os_family"] == "Debian":
  58. lt = ["0.2.4-0ubuntu1", "0.2.4.1-0ubuntu1"]
  59. eq = ["0.2.4-0ubuntu1", "0.2.4-0ubuntu1"]
  60. gt = ["0.2.4.1-0ubuntu1", "0.2.4-0ubuntu1"]
  61. elif grains["os_family"] == "Suse":
  62. lt = ["2.3.0-1", "2.3.1-15.1"]
  63. eq = ["2.3.1-15.1", "2.3.1-15.1"]
  64. gt = ["2.3.2-15.1", "2.3.1-15.1"]
  65. else:
  66. lt = ["2.3.0", "2.3.1"]
  67. eq = ["2.3.1", "2.3.1"]
  68. gt = ["2.3.2", "2.3.1"]
  69. self.assertEqual(self.run_function(func, lt), -1)
  70. self.assertEqual(self.run_function(func, eq), 0)
  71. self.assertEqual(self.run_function(func, gt), 1)
  72. @destructiveTest
  73. @requires_salt_modules("pkg.mod_repo", "pkg.del_repo", "pkg.get_repo")
  74. @requires_network()
  75. @requires_system_grains
  76. @slowTest
  77. def test_mod_del_repo(self, grains):
  78. """
  79. test modifying and deleting a software repository
  80. """
  81. repo = None
  82. try:
  83. if grains["os"] == "Ubuntu":
  84. repo = "ppa:otto-kesselgulasch/gimp-edge"
  85. uri = "http://ppa.launchpad.net/otto-kesselgulasch/gimp-edge/ubuntu"
  86. ret = self.run_function("pkg.mod_repo", [repo, "comps=main"])
  87. self.assertNotEqual(ret, {})
  88. ret = self.run_function("pkg.get_repo", [repo])
  89. self.assertIsInstance(
  90. ret,
  91. dict,
  92. "The 'pkg.get_repo' command did not return the excepted dictionary. "
  93. "Output:\n{}".format(ret),
  94. )
  95. self.assertEqual(
  96. ret["uri"],
  97. uri,
  98. msg="The URI did not match. Full return:\n{}".format(
  99. pprint.pformat(ret)
  100. ),
  101. )
  102. elif grains["os_family"] == "RedHat":
  103. repo = "saltstack"
  104. name = "SaltStack repo for RHEL/CentOS {0}".format(
  105. grains["osmajorrelease"]
  106. )
  107. baseurl = "http://repo.saltstack.com/yum/redhat/{0}/x86_64/latest/".format(
  108. grains["osmajorrelease"]
  109. )
  110. gpgkey = "https://repo.saltstack.com/yum/rhel{0}/SALTSTACK-GPG-KEY.pub".format(
  111. grains["osmajorrelease"]
  112. )
  113. gpgcheck = 1
  114. enabled = 1
  115. ret = self.run_function(
  116. "pkg.mod_repo",
  117. [repo],
  118. name=name,
  119. baseurl=baseurl,
  120. gpgkey=gpgkey,
  121. gpgcheck=gpgcheck,
  122. enabled=enabled,
  123. )
  124. # return data from pkg.mod_repo contains the file modified at
  125. # the top level, so use next(iter(ret)) to get that key
  126. self.assertNotEqual(ret, {})
  127. repo_info = ret[next(iter(ret))]
  128. self.assertIn(repo, repo_info)
  129. self.assertEqual(repo_info[repo]["baseurl"], baseurl)
  130. ret = self.run_function("pkg.get_repo", [repo])
  131. self.assertEqual(ret["baseurl"], baseurl)
  132. finally:
  133. if repo is not None:
  134. self.run_function("pkg.del_repo", [repo])
  135. @slowTest
  136. def test_mod_del_repo_multiline_values(self):
  137. """
  138. test modifying and deleting a software repository defined with multiline values
  139. """
  140. os_grain = self.run_function("grains.item", ["os"])["os"]
  141. repo = None
  142. try:
  143. if os_grain in ["CentOS", "RedHat"]:
  144. my_baseurl = (
  145. "http://my.fake.repo/foo/bar/\n http://my.fake.repo.alt/foo/bar/"
  146. )
  147. expected_get_repo_baseurl = (
  148. "http://my.fake.repo/foo/bar/\nhttp://my.fake.repo.alt/foo/bar/"
  149. )
  150. major_release = int(
  151. self.run_function("grains.item", ["osmajorrelease"])[
  152. "osmajorrelease"
  153. ]
  154. )
  155. repo = "fakerepo"
  156. name = "Fake repo for RHEL/CentOS/SUSE"
  157. baseurl = my_baseurl
  158. gpgkey = "https://my.fake.repo/foo/bar/MY-GPG-KEY.pub"
  159. failovermethod = "priority"
  160. gpgcheck = 1
  161. enabled = 1
  162. ret = self.run_function(
  163. "pkg.mod_repo",
  164. [repo],
  165. name=name,
  166. baseurl=baseurl,
  167. gpgkey=gpgkey,
  168. gpgcheck=gpgcheck,
  169. enabled=enabled,
  170. failovermethod=failovermethod,
  171. )
  172. # return data from pkg.mod_repo contains the file modified at
  173. # the top level, so use next(iter(ret)) to get that key
  174. self.assertNotEqual(ret, {})
  175. repo_info = ret[next(iter(ret))]
  176. self.assertIn(repo, repo_info)
  177. self.assertEqual(repo_info[repo]["baseurl"], my_baseurl)
  178. ret = self.run_function("pkg.get_repo", [repo])
  179. self.assertEqual(ret["baseurl"], expected_get_repo_baseurl)
  180. self.run_function("pkg.mod_repo", [repo])
  181. ret = self.run_function("pkg.get_repo", [repo])
  182. self.assertEqual(ret["baseurl"], expected_get_repo_baseurl)
  183. finally:
  184. if repo is not None:
  185. self.run_function("pkg.del_repo", [repo])
  186. @requires_salt_modules("pkg.owner")
  187. def test_owner(self):
  188. """
  189. test finding the package owning a file
  190. """
  191. func = "pkg.owner"
  192. ret = self.run_function(func, ["/bin/ls"])
  193. self.assertNotEqual(len(ret), 0)
  194. # Similar to pkg.owner, but for FreeBSD's pkgng
  195. @requires_salt_modules("pkg.which")
  196. def test_which(self):
  197. """
  198. test finding the package owning a file
  199. """
  200. func = "pkg.which"
  201. ret = self.run_function(func, ["/usr/local/bin/salt-call"])
  202. self.assertNotEqual(len(ret), 0)
  203. @destructiveTest
  204. @requires_salt_modules("pkg.version", "pkg.install", "pkg.remove")
  205. @requires_network()
  206. @slowTest
  207. def test_install_remove(self):
  208. """
  209. successfully install and uninstall a package
  210. """
  211. version = self.run_function("pkg.version", [self.pkg])
  212. def test_install():
  213. install_ret = self.run_function("pkg.install", [self.pkg])
  214. self.assertIn(self.pkg, install_ret)
  215. def test_remove():
  216. remove_ret = self.run_function("pkg.remove", [self.pkg])
  217. self.assertIn(self.pkg, remove_ret)
  218. if version and isinstance(version, dict):
  219. version = version[self.pkg]
  220. if version:
  221. test_remove()
  222. test_install()
  223. else:
  224. test_install()
  225. test_remove()
  226. @destructiveTest
  227. @requires_salt_modules(
  228. "pkg.hold",
  229. "pkg.unhold",
  230. "pkg.install",
  231. "pkg.version",
  232. "pkg.remove",
  233. "pkg.list_pkgs",
  234. )
  235. @requires_salt_states("pkg.installed")
  236. @requires_network()
  237. @requires_system_grains
  238. @slowTest
  239. def test_hold_unhold(self, grains):
  240. """
  241. test holding and unholding a package
  242. """
  243. versionlock_pkg = None
  244. if grains["os_family"] == "RedHat":
  245. pkgs = {
  246. p for p in self.run_function("pkg.list_pkgs") if "-versionlock" in p
  247. }
  248. if not pkgs:
  249. self.skipTest("No versionlock package found in repositories")
  250. for versionlock_pkg in pkgs:
  251. ret = self.run_state(
  252. "pkg.installed", name=versionlock_pkg, refresh=False
  253. )
  254. # Exit loop if a versionlock package installed correctly
  255. try:
  256. self.assertSaltTrueReturn(ret)
  257. break
  258. except AssertionError:
  259. pass
  260. else:
  261. self.fail("Could not install versionlock package from {}".format(pkgs))
  262. self.run_function("pkg.install", [self.pkg])
  263. try:
  264. hold_ret = self.run_function("pkg.hold", [self.pkg])
  265. if versionlock_pkg and "-versionlock is not installed" in str(hold_ret):
  266. self.skipTest("{} `{}` is installed".format(hold_ret, versionlock_pkg))
  267. self.assertIn(self.pkg, hold_ret)
  268. self.assertTrue(hold_ret[self.pkg]["result"])
  269. unhold_ret = self.run_function("pkg.unhold", [self.pkg])
  270. self.assertIn(self.pkg, unhold_ret)
  271. self.assertTrue(unhold_ret[self.pkg]["result"])
  272. self.run_function("pkg.remove", [self.pkg])
  273. finally:
  274. if versionlock_pkg:
  275. ret = self.run_state("pkg.removed", name=versionlock_pkg)
  276. self.assertSaltTrueReturn(ret)
  277. @destructiveTest
  278. @requires_salt_modules("pkg.refresh_db")
  279. @requires_network()
  280. @requires_system_grains
  281. @slowTest
  282. def test_refresh_db(self, grains):
  283. """
  284. test refreshing the package database
  285. """
  286. func = "pkg.refresh_db"
  287. rtag = salt.utils.pkg.rtag(self.minion_opts)
  288. salt.utils.pkg.write_rtag(self.minion_opts)
  289. self.assertTrue(os.path.isfile(rtag))
  290. ret = self.run_function(func)
  291. if not isinstance(ret, dict):
  292. self.skipTest(
  293. "Upstream repo did not return coherent results: {}".format(ret)
  294. )
  295. if grains["os_family"] == "RedHat":
  296. self.assertIn(ret, (True, None))
  297. elif grains["os_family"] == "Suse":
  298. if not isinstance(ret, dict):
  299. self.skipTest(
  300. "Upstream repo did not return coherent results. Skipping test."
  301. )
  302. self.assertNotEqual(ret, {})
  303. for source, state in ret.items():
  304. self.assertIn(state, (True, False, None))
  305. self.assertFalse(os.path.isfile(rtag))
  306. @requires_salt_modules("pkg.info_installed")
  307. @requires_system_grains
  308. @slowTest
  309. def test_pkg_info(self, grains):
  310. """
  311. Test returning useful information on Ubuntu systems.
  312. """
  313. func = "pkg.info_installed"
  314. if grains["os_family"] == "Debian":
  315. ret = self.run_function(func, ["bash", "dpkg"])
  316. keys = ret.keys()
  317. self.assertIn("bash", keys)
  318. self.assertIn("dpkg", keys)
  319. elif grains["os_family"] == "RedHat":
  320. ret = self.run_function(func, ["rpm", "bash"])
  321. keys = ret.keys()
  322. self.assertIn("rpm", keys)
  323. self.assertIn("bash", keys)
  324. elif grains["os_family"] == "Suse":
  325. ret = self.run_function(func, ["less", "zypper"])
  326. keys = ret.keys()
  327. self.assertIn("less", keys)
  328. self.assertIn("zypper", keys)
  329. else:
  330. ret = self.run_function(func, [self.pkg])
  331. keys = ret.keys()
  332. self.assertIn(self.pkg, keys)
  333. @destructiveTest
  334. @requires_network()
  335. @requires_salt_modules(
  336. "pkg.refresh_db",
  337. "pkg.upgrade",
  338. "pkg.install",
  339. "pkg.list_repo_pkgs",
  340. "pkg.list_upgrades",
  341. )
  342. @requires_system_grains
  343. @slowTest
  344. def test_pkg_upgrade_has_pending_upgrades(self, grains):
  345. """
  346. Test running a system upgrade when there are packages that need upgrading
  347. """
  348. if grains["os"] == "Arch":
  349. self.skipTest("Arch moved to Python 3.8 and we're not ready for it yet")
  350. func = "pkg.upgrade"
  351. # First make sure that an up-to-date copy of the package db is available
  352. self.run_function("pkg.refresh_db")
  353. if grains["os_family"] == "Suse":
  354. # This test assumes that there are multiple possible versions of a
  355. # package available. That makes it brittle if you pick just one
  356. # target, as changes in the available packages will break the test.
  357. # Therefore, we'll choose from several packages to make sure we get
  358. # one that is suitable for this test.
  359. packages = ("hwinfo", "avrdude", "diffoscope", "vim")
  360. available = self.run_function("pkg.list_repo_pkgs", packages)
  361. for package in packages:
  362. try:
  363. new, old = available[package][:2]
  364. except (KeyError, ValueError):
  365. # Package not available, or less than 2 versions
  366. # available. This is not a suitable target.
  367. continue
  368. else:
  369. target = package
  370. break
  371. else:
  372. # None of the packages have more than one version available, so
  373. # we need to find new package(s). pkg.list_repo_pkgs can be
  374. # used to get an overview of the available packages. We should
  375. # try to find packages with few dependencies and small download
  376. # sizes, to keep this test from taking longer than necessary.
  377. self.fail("No suitable package found for this test")
  378. # Make sure we have the 2nd-oldest available version installed
  379. ret = self.run_function("pkg.install", [target], version=old)
  380. if not isinstance(ret, dict):
  381. if ret.startswith("ERROR"):
  382. self.skipTest(
  383. "Could not install older {0} to complete "
  384. "test.".format(target)
  385. )
  386. # Run a system upgrade, which should catch the fact that the
  387. # targeted package needs upgrading, and upgrade it.
  388. ret = self.run_function(func)
  389. # The changes dictionary should not be empty.
  390. if "changes" in ret:
  391. self.assertIn(target, ret["changes"])
  392. else:
  393. self.assertIn(target, ret)
  394. else:
  395. ret = self.run_function("pkg.list_upgrades")
  396. if ret == "" or ret == {}:
  397. self.skipTest(
  398. "No updates available for this machine. Skipping pkg.upgrade test."
  399. )
  400. else:
  401. args = []
  402. if grains["os_family"] == "Debian":
  403. args = ["dist_upgrade=True"]
  404. ret = self.run_function(func, args)
  405. self.assertNotEqual(ret, {})
  406. @destructiveTest
  407. @skipIf(
  408. salt.utils.platform.is_darwin(),
  409. "The jenkins user is equivalent to root on mac, causing the test to be unrunnable",
  410. )
  411. @requires_salt_modules("pkg.remove", "pkg.latest_version")
  412. @requires_salt_states("pkg.removed")
  413. @requires_system_grains
  414. @slowTest
  415. def test_pkg_latest_version(self, grains):
  416. """
  417. Check that pkg.latest_version returns the latest version of the uninstalled package.
  418. The package is not installed. Only the package version is checked.
  419. """
  420. self.run_state("pkg.removed", name=self.pkg)
  421. cmd_pkg = []
  422. if grains["os_family"] == "RedHat":
  423. cmd_pkg = self.run_function("cmd.run", ["yum list {0}".format(self.pkg)])
  424. elif salt.utils.platform.is_windows():
  425. cmd_pkg = self.run_function("pkg.list_available", [self.pkg])
  426. elif grains["os_family"] == "Debian":
  427. cmd_pkg = self.run_function("cmd.run", ["apt list {0}".format(self.pkg)])
  428. elif grains["os_family"] == "Arch":
  429. cmd_pkg = self.run_function("cmd.run", ["pacman -Si {0}".format(self.pkg)])
  430. elif grains["os_family"] == "FreeBSD":
  431. cmd_pkg = self.run_function(
  432. "cmd.run", ["pkg search -S name -qQ version -e {0}".format(self.pkg)]
  433. )
  434. elif grains["os_family"] == "Suse":
  435. cmd_pkg = self.run_function("cmd.run", ["zypper info {0}".format(self.pkg)])
  436. elif grains["os_family"] == "MacOS":
  437. brew_bin = salt.utils.path.which("brew")
  438. mac_user = self.run_function("file.get_user", [brew_bin])
  439. if mac_user == "root":
  440. self.skipTest(
  441. "brew cannot run as root, try a user in {}".format(
  442. os.listdir("/Users/")
  443. )
  444. )
  445. cmd_pkg = self.run_function(
  446. "cmd.run", ["brew info {0}".format(self.pkg)], run_as=mac_user
  447. )
  448. else:
  449. self.skipTest(
  450. "TODO: test not configured for {}".format(grains["os_family"])
  451. )
  452. pkg_latest = self.run_function("pkg.latest_version", [self.pkg])
  453. self.assertIn(pkg_latest, cmd_pkg)