PageRenderTime 58ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/sos/policies/__init__.py

https://gitlab.com/pbandark/sos
Python | 459 lines | 421 code | 17 blank | 21 comment | 13 complexity | 6fce5b2c655c159795655406a3352520 MD5 | raw file
  1. from __future__ import with_statement
  2. import os
  3. import re
  4. import platform
  5. import time
  6. import fnmatch
  7. import tempfile
  8. from os import environ
  9. from sos.utilities import (ImporterHelper,
  10. import_module,
  11. shell_out)
  12. from sos.plugins import IndependentPlugin, ExperimentalPlugin
  13. from sos import _sos as _
  14. from textwrap import fill
  15. from six import print_
  16. from six.moves import input
  17. def import_policy(name):
  18. policy_fqname = "sos.policies.%s" % name
  19. try:
  20. return import_module(policy_fqname, Policy)
  21. except ImportError:
  22. return None
  23. def load(cache={}, sysroot=None):
  24. if 'policy' in cache:
  25. return cache.get('policy')
  26. import sos.policies
  27. helper = ImporterHelper(sos.policies)
  28. for module in helper.get_modules():
  29. for policy in import_policy(module):
  30. if policy.check():
  31. cache['policy'] = policy(sysroot=sysroot)
  32. if 'policy' not in cache:
  33. cache['policy'] = GenericPolicy()
  34. return cache['policy']
  35. class PackageManager(object):
  36. """Encapsulates a package manager. If you provide a query_command to the
  37. constructor it should print each package on the system in the following
  38. format::
  39. package name|package.version
  40. You may also subclass this class and provide a get_pkg_list method to
  41. build the list of packages and versions.
  42. """
  43. query_command = None
  44. timeout = 30
  45. chroot = None
  46. def __init__(self, query_command=None, chroot=None):
  47. self.packages = {}
  48. if query_command:
  49. self.query_command = query_command
  50. if chroot:
  51. self.chroot = chroot
  52. def all_pkgs_by_name(self, name):
  53. """
  54. Return a list of packages that match name.
  55. """
  56. return fnmatch.filter(self.all_pkgs().keys(), name)
  57. def all_pkgs_by_name_regex(self, regex_name, flags=0):
  58. """
  59. Return a list of packages that match regex_name.
  60. """
  61. reg = re.compile(regex_name, flags)
  62. return [pkg for pkg in self.all_pkgs().keys() if reg.match(pkg)]
  63. def pkg_by_name(self, name):
  64. """
  65. Return a single package that matches name.
  66. """
  67. pkgmatches = self.all_pkgs_by_name(name)
  68. if (len(pkgmatches) != 0):
  69. return self.all_pkgs_by_name(name)[-1]
  70. else:
  71. return None
  72. def get_pkg_list(self):
  73. """Returns a dictionary of packages in the following
  74. format::
  75. {'package_name': {'name': 'package_name',
  76. 'version': 'major.minor.version'}}
  77. """
  78. if self.query_command:
  79. cmd = self.query_command
  80. pkg_list = shell_out(
  81. cmd, timeout=self.timeout, chroot=self.chroot
  82. ).splitlines()
  83. for pkg in pkg_list:
  84. if '|' not in pkg:
  85. continue
  86. name, version = pkg.split("|")
  87. self.packages[name] = {
  88. 'name': name,
  89. 'version': version.split(".")
  90. }
  91. return self.packages
  92. def all_pkgs(self):
  93. """
  94. Return a list of all packages.
  95. """
  96. if not self.packages:
  97. self.packages = self.get_pkg_list()
  98. return self.packages
  99. def pkg_nvra(self, pkg):
  100. fields = pkg.split("-")
  101. version, release, arch = fields[-3:]
  102. name = "-".join(fields[:-3])
  103. return (name, version, release, arch)
  104. class Policy(object):
  105. msg = _("""\
  106. This command will collect system configuration and diagnostic information \
  107. from this %(distro)s system. An archive containing the collected information \
  108. will be generated in %(tmpdir)s.
  109. For more information on %(vendor)s visit:
  110. %(vendor_url)s
  111. The generated archive may contain data considered sensitive and its content \
  112. should be reviewed by the originating organization before being passed to \
  113. any third party.
  114. No changes will be made to system configuration.
  115. %(vendor_text)s
  116. """)
  117. distro = "Unknown"
  118. vendor = "Unknown"
  119. vendor_url = "http://www.example.com/"
  120. vendor_text = ""
  121. PATH = ""
  122. _in_container = False
  123. _host_sysroot = '/'
  124. def __init__(self, sysroot=None):
  125. """Subclasses that choose to override this initializer should call
  126. super() to ensure that they get the required platform bits attached.
  127. super(SubClass, self).__init__(). Policies that require runtime
  128. tests to construct PATH must call self.set_exec_path() after
  129. modifying PATH in their own initializer."""
  130. self._parse_uname()
  131. self.report_name = self.hostname
  132. self.case_id = None
  133. self.package_manager = PackageManager()
  134. self._valid_subclasses = []
  135. self.set_exec_path()
  136. self._host_sysroot = sysroot
  137. def get_valid_subclasses(self):
  138. return [IndependentPlugin] + self._valid_subclasses
  139. def set_valid_subclasses(self, subclasses):
  140. self._valid_subclasses = subclasses
  141. def del_valid_subclasses(self):
  142. del self._valid_subclasses
  143. valid_subclasses = property(get_valid_subclasses,
  144. set_valid_subclasses,
  145. del_valid_subclasses,
  146. "list of subclasses that this policy can "
  147. "process")
  148. def check(self):
  149. """
  150. This function is responsible for determining if the underlying system
  151. is supported by this policy.
  152. """
  153. return False
  154. def in_container(self):
  155. """ Returns True if sos is running inside a container environment.
  156. """
  157. return self._in_container
  158. def host_sysroot(self):
  159. return self._host_sysroot
  160. def dist_version(self):
  161. """
  162. Return the OS version
  163. """
  164. pass
  165. def get_preferred_archive(self):
  166. """
  167. Return the class object of the prefered archive format for this
  168. platform
  169. """
  170. from sos.archive import TarFileArchive
  171. return TarFileArchive
  172. def get_archive_name(self):
  173. """
  174. This function should return the filename of the archive without the
  175. extension.
  176. """
  177. if self.case_id:
  178. self.report_name += "." + self.case_id
  179. return "sosreport-%s-%s" % (self.report_name,
  180. time.strftime("%Y%m%d%H%M%S"))
  181. def get_tmp_dir(self, opt_tmp_dir):
  182. if not opt_tmp_dir:
  183. return tempfile.gettempdir()
  184. return opt_tmp_dir
  185. def match_plugin(self, plugin_classes):
  186. if len(plugin_classes) > 1:
  187. for p in plugin_classes:
  188. # Give preference to the first listed tagging class
  189. # so that e.g. UbuntuPlugin is chosen over DebianPlugin
  190. # on an Ubuntu installation.
  191. if issubclass(p, self.valid_subclasses[0]):
  192. return p
  193. return plugin_classes[0]
  194. def validate_plugin(self, plugin_class, experimental=False):
  195. """
  196. Verifies that the plugin_class should execute under this policy
  197. """
  198. valid_subclasses = [IndependentPlugin] + self.valid_subclasses
  199. if experimental:
  200. valid_subclasses += [ExperimentalPlugin]
  201. return any(issubclass(plugin_class, class_) for
  202. class_ in valid_subclasses)
  203. def pre_work(self):
  204. """
  205. This function is called prior to collection.
  206. """
  207. pass
  208. def post_work(self):
  209. """
  210. This function is called after the sosreport has been generated.
  211. """
  212. pass
  213. def pkg_by_name(self, pkg):
  214. return self.package_manager.pkg_by_name(pkg)
  215. def _parse_uname(self):
  216. (system, node, release,
  217. version, machine, processor) = platform.uname()
  218. self.system = system
  219. self.hostname = node
  220. self.release = release
  221. self.smp = version.split()[1] == "SMP"
  222. self.machine = machine
  223. def set_commons(self, commons):
  224. self.commons = commons
  225. def _set_PATH(self, path):
  226. environ['PATH'] = path
  227. def set_exec_path(self):
  228. self._set_PATH(self.PATH)
  229. def is_root(self):
  230. """This method should return true if the user calling the script is
  231. considered to be a superuser"""
  232. return (os.getuid() == 0)
  233. def get_preferred_hash_name(self):
  234. """Returns the string name of the hashlib-supported checksum algorithm
  235. to use"""
  236. return "md5"
  237. def display_results(self, archive, directory, checksum):
  238. # Display results is called from the tail of SoSReport.final_work()
  239. #
  240. # Logging is already shutdown and all terminal output must use the
  241. # print() call.
  242. # make sure a report exists
  243. if not archive and not directory:
  244. return False
  245. self._print()
  246. if archive:
  247. self._print(_("Your sosreport has been generated and saved "
  248. "in:\n %s") % archive)
  249. else:
  250. self._print(_("sosreport build tree is located at : %s" %
  251. directory))
  252. self._print()
  253. if checksum:
  254. self._print(_("The checksum is: ") + checksum)
  255. self._print()
  256. self._print(_("Please send this file to your support "
  257. "representative."))
  258. self._print()
  259. def _print(self, msg=None):
  260. """A wrapper around print that only prints if we are not running in
  261. quiet mode"""
  262. if not self.commons['cmdlineopts'].quiet:
  263. if msg:
  264. print_(msg)
  265. else:
  266. print_()
  267. def get_msg(self):
  268. """This method is used to prepare the preamble text to display to
  269. the user in non-batch mode. If your policy sets self.distro that
  270. text will be substituted accordingly. You can also override this
  271. method to do something more complicated."""
  272. width = 72
  273. _msg = self.msg % {'distro': self.distro, 'vendor': self.vendor,
  274. 'vendor_url': self.vendor_url,
  275. 'vendor_text': self.vendor_text,
  276. 'tmpdir': self.commons['tmpdir']}
  277. _fmt = ""
  278. for line in _msg.splitlines():
  279. _fmt = _fmt + fill(line, width, replace_whitespace=False) + '\n'
  280. return _fmt
  281. class GenericPolicy(Policy):
  282. """This Policy will be returned if no other policy can be loaded. This
  283. should allow for IndependentPlugins to be executed on any system"""
  284. def get_msg(self):
  285. return self.msg % {'distro': self.system}
  286. class LinuxPolicy(Policy):
  287. """This policy is meant to be an abc class that provides common
  288. implementations used in Linux distros"""
  289. distro = "Linux"
  290. vendor = "None"
  291. PATH = "/bin:/sbin:/usr/bin:/usr/sbin"
  292. _preferred_hash_name = None
  293. def __init__(self, sysroot=None):
  294. super(LinuxPolicy, self).__init__(sysroot=sysroot)
  295. def get_preferred_hash_name(self):
  296. if self._preferred_hash_name:
  297. return self._preferred_hash_name
  298. checksum = "md5"
  299. try:
  300. fp = open("/proc/sys/crypto/fips_enabled", "r")
  301. except:
  302. self._preferred_hash_name = checksum
  303. return checksum
  304. fips_enabled = fp.read()
  305. if fips_enabled.find("1") >= 0:
  306. checksum = "sha256"
  307. fp.close()
  308. self._preferred_hash_name = checksum
  309. return checksum
  310. def default_runlevel(self):
  311. try:
  312. with open("/etc/inittab") as fp:
  313. pattern = r"id:(\d{1}):initdefault:"
  314. text = fp.read()
  315. return int(re.findall(pattern, text)[0])
  316. except:
  317. return 3
  318. def kernel_version(self):
  319. return self.release
  320. def host_name(self):
  321. return self.hostname
  322. def is_kernel_smp(self):
  323. return self.smp
  324. def get_arch(self):
  325. return self.machine
  326. def get_local_name(self):
  327. """Returns the name usd in the pre_work step"""
  328. return self.host_name()
  329. def sanitize_report_name(self, report_name):
  330. return re.sub(r"[^-a-zA-Z.0-9]", "", report_name)
  331. def sanitize_case_id(self, case_id):
  332. return re.sub(r"[^-a-z,A-Z.0-9]", "", case_id)
  333. def pre_work(self):
  334. # this method will be called before the gathering begins
  335. cmdline_opts = self.commons['cmdlineopts']
  336. customer_name = cmdline_opts.customer_name
  337. localname = customer_name if customer_name else self.get_local_name()
  338. caseid = cmdline_opts.case_id if cmdline_opts.case_id else ""
  339. if not cmdline_opts.batch and not \
  340. cmdline_opts.quiet:
  341. try:
  342. self.report_name = input(_("Please enter your first initial "
  343. "and last name [%s]: ") % localname)
  344. self.case_id = input(_("Please enter the case id "
  345. "that you are generating this "
  346. "report for [%s]: ") % caseid)
  347. self._print()
  348. except:
  349. self._print()
  350. self.report_name = localname
  351. if len(self.report_name) == 0:
  352. self.report_name = localname
  353. if customer_name:
  354. self.report_name = customer_name
  355. if cmdline_opts.case_id:
  356. self.case_id = cmdline_opts.case_id
  357. self.report_name = self.sanitize_report_name(self.report_name)
  358. if self.case_id:
  359. self.case_id = self.sanitize_case_id(self.case_id)
  360. if (self.report_name == ""):
  361. self.report_name = "default"
  362. return
  363. # vim: set et ts=4 sw=4 :