test_pkg.py 18 KB

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