PageRenderTime 64ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/v2/ansible/module_utils/facts.py

https://gitlab.com/18runt88/ansible
Python | 1098 lines | 1022 code | 29 blank | 47 comment | 66 complexity | f2a4315605397bcfcc91a6930557d052 MD5 | raw file
  1. # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
  2. #
  3. # This file is part of Ansible
  4. #
  5. # Ansible is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # Ansible is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
  17. import os
  18. import stat
  19. import array
  20. import errno
  21. import fcntl
  22. import fnmatch
  23. import glob
  24. import platform
  25. import re
  26. import signal
  27. import socket
  28. import struct
  29. import datetime
  30. import getpass
  31. import pwd
  32. import ConfigParser
  33. import StringIO
  34. from string import maketrans
  35. try:
  36. import selinux
  37. HAVE_SELINUX=True
  38. except ImportError:
  39. HAVE_SELINUX=False
  40. try:
  41. import json
  42. except ImportError:
  43. import simplejson as json
  44. # --------------------------------------------------------------
  45. # timeout function to make sure some fact gathering
  46. # steps do not exceed a time limit
  47. class TimeoutError(Exception):
  48. pass
  49. def timeout(seconds=10, error_message="Timer expired"):
  50. def decorator(func):
  51. def _handle_timeout(signum, frame):
  52. raise TimeoutError(error_message)
  53. def wrapper(*args, **kwargs):
  54. signal.signal(signal.SIGALRM, _handle_timeout)
  55. signal.alarm(seconds)
  56. try:
  57. result = func(*args, **kwargs)
  58. finally:
  59. signal.alarm(0)
  60. return result
  61. return wrapper
  62. return decorator
  63. # --------------------------------------------------------------
  64. class Facts(object):
  65. """
  66. This class should only attempt to populate those facts that
  67. are mostly generic to all systems. This includes platform facts,
  68. service facts (e.g. ssh keys or selinux), and distribution facts.
  69. Anything that requires extensive code or may have more than one
  70. possible implementation to establish facts for a given topic should
  71. subclass Facts.
  72. """
  73. # i86pc is a Solaris and derivatives-ism
  74. _I386RE = re.compile(r'i([3456]86|86pc)')
  75. # For the most part, we assume that platform.dist() will tell the truth.
  76. # This is the fallback to handle unknowns or exceptions
  77. OSDIST_LIST = ( ('/etc/oracle-release', 'OracleLinux'),
  78. ('/etc/redhat-release', 'RedHat'),
  79. ('/etc/vmware-release', 'VMwareESX'),
  80. ('/etc/openwrt_release', 'OpenWrt'),
  81. ('/etc/system-release', 'OtherLinux'),
  82. ('/etc/alpine-release', 'Alpine'),
  83. ('/etc/release', 'Solaris'),
  84. ('/etc/arch-release', 'Archlinux'),
  85. ('/etc/SuSE-release', 'SuSE'),
  86. ('/etc/os-release', 'SuSE'),
  87. ('/etc/gentoo-release', 'Gentoo'),
  88. ('/etc/os-release', 'Debian'),
  89. ('/etc/os-release', 'NA'),
  90. ('/etc/lsb-release', 'Mandriva'))
  91. SELINUX_MODE_DICT = { 1: 'enforcing', 0: 'permissive', -1: 'disabled' }
  92. # A list of dicts. If there is a platform with more than one
  93. # package manager, put the preferred one last. If there is an
  94. # ansible module, use that as the value for the 'name' key.
  95. PKG_MGRS = [ { 'path' : '/usr/bin/yum', 'name' : 'yum' },
  96. { 'path' : '/usr/bin/dnf', 'name' : 'dnf' },
  97. { 'path' : '/usr/bin/apt-get', 'name' : 'apt' },
  98. { 'path' : '/usr/bin/zypper', 'name' : 'zypper' },
  99. { 'path' : '/usr/sbin/urpmi', 'name' : 'urpmi' },
  100. { 'path' : '/usr/bin/pacman', 'name' : 'pacman' },
  101. { 'path' : '/bin/opkg', 'name' : 'opkg' },
  102. { 'path' : '/opt/local/bin/pkgin', 'name' : 'pkgin' },
  103. { 'path' : '/opt/local/bin/port', 'name' : 'macports' },
  104. { 'path' : '/sbin/apk', 'name' : 'apk' },
  105. { 'path' : '/usr/sbin/pkg', 'name' : 'pkgng' },
  106. { 'path' : '/usr/sbin/swlist', 'name' : 'SD-UX' },
  107. { 'path' : '/usr/bin/emerge', 'name' : 'portage' },
  108. { 'path' : '/usr/sbin/pkgadd', 'name' : 'svr4pkg' },
  109. { 'path' : '/usr/bin/pkg', 'name' : 'pkg' },
  110. ]
  111. def __init__(self, load_on_init=True):
  112. self.facts = {}
  113. if load_on_init:
  114. self.get_platform_facts()
  115. self.get_distribution_facts()
  116. self.get_cmdline()
  117. self.get_public_ssh_host_keys()
  118. self.get_selinux_facts()
  119. self.get_fips_facts()
  120. self.get_pkg_mgr_facts()
  121. self.get_lsb_facts()
  122. self.get_date_time_facts()
  123. self.get_user_facts()
  124. self.get_local_facts()
  125. self.get_env_facts()
  126. def populate(self):
  127. return self.facts
  128. # Platform
  129. # platform.system() can be Linux, Darwin, Java, or Windows
  130. def get_platform_facts(self):
  131. self.facts['system'] = platform.system()
  132. self.facts['kernel'] = platform.release()
  133. self.facts['machine'] = platform.machine()
  134. self.facts['python_version'] = platform.python_version()
  135. self.facts['fqdn'] = socket.getfqdn()
  136. self.facts['hostname'] = platform.node().split('.')[0]
  137. self.facts['nodename'] = platform.node()
  138. self.facts['domain'] = '.'.join(self.facts['fqdn'].split('.')[1:])
  139. arch_bits = platform.architecture()[0]
  140. self.facts['userspace_bits'] = arch_bits.replace('bit', '')
  141. if self.facts['machine'] == 'x86_64':
  142. self.facts['architecture'] = self.facts['machine']
  143. if self.facts['userspace_bits'] == '64':
  144. self.facts['userspace_architecture'] = 'x86_64'
  145. elif self.facts['userspace_bits'] == '32':
  146. self.facts['userspace_architecture'] = 'i386'
  147. elif Facts._I386RE.search(self.facts['machine']):
  148. self.facts['architecture'] = 'i386'
  149. if self.facts['userspace_bits'] == '64':
  150. self.facts['userspace_architecture'] = 'x86_64'
  151. elif self.facts['userspace_bits'] == '32':
  152. self.facts['userspace_architecture'] = 'i386'
  153. else:
  154. self.facts['architecture'] = self.facts['machine']
  155. if self.facts['system'] == 'Linux':
  156. self.get_distribution_facts()
  157. elif self.facts['system'] == 'AIX':
  158. try:
  159. rc, out, err = module.run_command("/usr/sbin/bootinfo -p")
  160. data = out.split('\n')
  161. self.facts['architecture'] = data[0]
  162. except:
  163. self.facts['architecture'] = 'Not Available'
  164. elif self.facts['system'] == 'OpenBSD':
  165. self.facts['architecture'] = platform.uname()[5]
  166. def get_local_facts(self):
  167. fact_path = module.params.get('fact_path', None)
  168. if not fact_path or not os.path.exists(fact_path):
  169. return
  170. local = {}
  171. for fn in sorted(glob.glob(fact_path + '/*.fact')):
  172. # where it will sit under local facts
  173. fact_base = os.path.basename(fn).replace('.fact','')
  174. if stat.S_IXUSR & os.stat(fn)[stat.ST_MODE]:
  175. # run it
  176. # try to read it as json first
  177. # if that fails read it with ConfigParser
  178. # if that fails, skip it
  179. rc, out, err = module.run_command(fn)
  180. else:
  181. out = get_file_content(fn, default='')
  182. # load raw json
  183. fact = 'loading %s' % fact_base
  184. try:
  185. fact = json.loads(out)
  186. except ValueError, e:
  187. # load raw ini
  188. cp = ConfigParser.ConfigParser()
  189. try:
  190. cp.readfp(StringIO.StringIO(out))
  191. except ConfigParser.Error, e:
  192. fact="error loading fact - please check content"
  193. else:
  194. fact = {}
  195. #print cp.sections()
  196. for sect in cp.sections():
  197. if sect not in fact:
  198. fact[sect] = {}
  199. for opt in cp.options(sect):
  200. val = cp.get(sect, opt)
  201. fact[sect][opt]=val
  202. local[fact_base] = fact
  203. if not local:
  204. return
  205. self.facts['local'] = local
  206. # platform.dist() is deprecated in 2.6
  207. # in 2.6 and newer, you should use platform.linux_distribution()
  208. def get_distribution_facts(self):
  209. # A list with OS Family members
  210. OS_FAMILY = dict(
  211. RedHat = 'RedHat', Fedora = 'RedHat', CentOS = 'RedHat', Scientific = 'RedHat',
  212. SLC = 'RedHat', Ascendos = 'RedHat', CloudLinux = 'RedHat', PSBM = 'RedHat',
  213. OracleLinux = 'RedHat', OVS = 'RedHat', OEL = 'RedHat', Amazon = 'RedHat',
  214. XenServer = 'RedHat', Ubuntu = 'Debian', Debian = 'Debian', Raspbian = 'Debian', SLES = 'Suse',
  215. SLED = 'Suse', openSUSE = 'Suse', SuSE = 'Suse', Gentoo = 'Gentoo', Funtoo = 'Gentoo',
  216. Archlinux = 'Archlinux', Mandriva = 'Mandrake', Mandrake = 'Mandrake',
  217. Solaris = 'Solaris', Nexenta = 'Solaris', OmniOS = 'Solaris', OpenIndiana = 'Solaris',
  218. SmartOS = 'Solaris', AIX = 'AIX', Alpine = 'Alpine', MacOSX = 'Darwin',
  219. FreeBSD = 'FreeBSD', HPUX = 'HP-UX'
  220. )
  221. # TODO: Rewrite this to use the function references in a dict pattern
  222. # as it's much cleaner than this massive if-else
  223. if self.facts['system'] == 'AIX':
  224. self.facts['distribution'] = 'AIX'
  225. rc, out, err = module.run_command("/usr/bin/oslevel")
  226. data = out.split('.')
  227. self.facts['distribution_version'] = data[0]
  228. self.facts['distribution_release'] = data[1]
  229. elif self.facts['system'] == 'HP-UX':
  230. self.facts['distribution'] = 'HP-UX'
  231. rc, out, err = module.run_command("/usr/sbin/swlist |egrep 'HPUX.*OE.*[AB].[0-9]+\.[0-9]+'", use_unsafe_shell=True)
  232. data = re.search('HPUX.*OE.*([AB].[0-9]+\.[0-9]+)\.([0-9]+).*', out)
  233. if data:
  234. self.facts['distribution_version'] = data.groups()[0]
  235. self.facts['distribution_release'] = data.groups()[1]
  236. elif self.facts['system'] == 'Darwin':
  237. self.facts['distribution'] = 'MacOSX'
  238. rc, out, err = module.run_command("/usr/bin/sw_vers -productVersion")
  239. data = out.split()[-1]
  240. self.facts['distribution_version'] = data
  241. elif self.facts['system'] == 'FreeBSD':
  242. self.facts['distribution'] = 'FreeBSD'
  243. self.facts['distribution_release'] = platform.release()
  244. self.facts['distribution_version'] = platform.version()
  245. elif self.facts['system'] == 'NetBSD':
  246. self.facts['distribution'] = 'NetBSD'
  247. self.facts['distribution_release'] = platform.release()
  248. self.facts['distribution_version'] = platform.version()
  249. elif self.facts['system'] == 'OpenBSD':
  250. self.facts['distribution'] = 'OpenBSD'
  251. self.facts['distribution_release'] = platform.release()
  252. rc, out, err = module.run_command("/sbin/sysctl -n kern.version")
  253. match = re.match('OpenBSD\s[0-9]+.[0-9]+-(\S+)\s.*', out)
  254. if match:
  255. self.facts['distribution_version'] = match.groups()[0]
  256. else:
  257. self.facts['distribution_version'] = 'release'
  258. else:
  259. dist = platform.dist()
  260. self.facts['distribution'] = dist[0].capitalize() or 'NA'
  261. self.facts['distribution_version'] = dist[1] or 'NA'
  262. self.facts['distribution_major_version'] = dist[1].split('.')[0] or 'NA'
  263. self.facts['distribution_release'] = dist[2] or 'NA'
  264. # Try to handle the exceptions now ...
  265. for (path, name) in Facts.OSDIST_LIST:
  266. if os.path.exists(path):
  267. if os.path.getsize(path) > 0:
  268. if self.facts['distribution'] in ('Fedora', ):
  269. # Once we determine the value is one of these distros
  270. # we trust the values are always correct
  271. break
  272. elif name == 'OracleLinux':
  273. data = get_file_content(path)
  274. if 'Oracle Linux' in data:
  275. self.facts['distribution'] = name
  276. else:
  277. self.facts['distribution'] = data.split()[0]
  278. break
  279. elif name == 'RedHat':
  280. data = get_file_content(path)
  281. if 'Red Hat' in data:
  282. self.facts['distribution'] = name
  283. else:
  284. self.facts['distribution'] = data.split()[0]
  285. break
  286. elif name == 'OtherLinux':
  287. data = get_file_content(path)
  288. if 'Amazon' in data:
  289. self.facts['distribution'] = 'Amazon'
  290. self.facts['distribution_version'] = data.split()[-1]
  291. break
  292. elif name == 'OpenWrt':
  293. data = get_file_content(path)
  294. if 'OpenWrt' in data:
  295. self.facts['distribution'] = name
  296. version = re.search('DISTRIB_RELEASE="(.*)"', data)
  297. if version:
  298. self.facts['distribution_version'] = version.groups()[0]
  299. release = re.search('DISTRIB_CODENAME="(.*)"', data)
  300. if release:
  301. self.facts['distribution_release'] = release.groups()[0]
  302. break
  303. elif name == 'Alpine':
  304. data = get_file_content(path)
  305. self.facts['distribution'] = name
  306. self.facts['distribution_version'] = data
  307. break
  308. elif name == 'Solaris':
  309. data = get_file_content(path).split('\n')[0]
  310. if 'Solaris' in data:
  311. ora_prefix = ''
  312. if 'Oracle Solaris' in data:
  313. data = data.replace('Oracle ','')
  314. ora_prefix = 'Oracle '
  315. self.facts['distribution'] = data.split()[0]
  316. self.facts['distribution_version'] = data.split()[1]
  317. self.facts['distribution_release'] = ora_prefix + data
  318. break
  319. uname_rc, uname_out, uname_err = module.run_command(['uname', '-v'])
  320. distribution_version = None
  321. if 'SmartOS' in data:
  322. self.facts['distribution'] = 'SmartOS'
  323. if os.path.exists('/etc/product'):
  324. product_data = dict([l.split(': ', 1) for l in get_file_content('/etc/product').split('\n') if ': ' in l])
  325. if 'Image' in product_data:
  326. distribution_version = product_data.get('Image').split()[-1]
  327. elif 'OpenIndiana' in data:
  328. self.facts['distribution'] = 'OpenIndiana'
  329. elif 'OmniOS' in data:
  330. self.facts['distribution'] = 'OmniOS'
  331. distribution_version = data.split()[-1]
  332. elif uname_rc == 0 and 'NexentaOS_' in uname_out:
  333. self.facts['distribution'] = 'Nexenta'
  334. distribution_version = data.split()[-1].lstrip('v')
  335. if self.facts['distribution'] in ('SmartOS', 'OpenIndiana', 'OmniOS', 'Nexenta'):
  336. self.facts['distribution_release'] = data.strip()
  337. if distribution_version is not None:
  338. self.facts['distribution_version'] = distribution_version
  339. elif uname_rc == 0:
  340. self.facts['distribution_version'] = uname_out.split('\n')[0].strip()
  341. break
  342. elif name == 'SuSE':
  343. data = get_file_content(path)
  344. if 'suse' in data.lower():
  345. if path == '/etc/os-release':
  346. for line in data.splitlines():
  347. distribution = re.search("^NAME=(.*)", line)
  348. if distribution:
  349. self.facts['distribution'] = distribution.group(1).strip('"')
  350. distribution_version = re.search('^VERSION_ID="?([0-9]+\.?[0-9]*)"?', line) # example pattern are 13.04 13.0 13
  351. if distribution_version:
  352. self.facts['distribution_version'] = distribution_version.group(1)
  353. if 'open' in data.lower():
  354. release = re.search("^PRETTY_NAME=[^(]+ \(?([^)]+?)\)", line)
  355. if release:
  356. self.facts['distribution_release'] = release.groups()[0]
  357. elif 'enterprise' in data.lower():
  358. release = re.search('^VERSION_ID="?[0-9]+\.?([0-9]*)"?', line) # SLES doesn't got funny release names
  359. if release:
  360. release = release.group(1)
  361. else:
  362. release = "0" # no minor number, so it is the first release
  363. self.facts['distribution_release'] = release
  364. break
  365. elif path == '/etc/SuSE-release':
  366. if 'open' in data.lower():
  367. data = data.splitlines()
  368. distdata = get_file_content(path).split('\n')[0]
  369. self.facts['distribution'] = distdata.split()[0]
  370. for line in data:
  371. release = re.search('CODENAME *= *([^\n]+)', line)
  372. if release:
  373. self.facts['distribution_release'] = release.groups()[0].strip()
  374. elif 'enterprise' in data.lower():
  375. lines = data.splitlines()
  376. distribution = lines[0].split()[0]
  377. if "Server" in data:
  378. self.facts['distribution'] = "SLES"
  379. elif "Desktop" in data:
  380. self.facts['distribution'] = "SLED"
  381. for line in lines:
  382. release = re.search('PATCHLEVEL = ([0-9]+)', line) # SLES doesn't got funny release names
  383. if release:
  384. self.facts['distribution_release'] = release.group(1)
  385. self.facts['distribution_version'] = self.facts['distribution_version'] + '.' + release.group(1)
  386. elif name == 'Debian':
  387. data = get_file_content(path)
  388. if 'Debian' in data or 'Raspbian' in data:
  389. release = re.search("PRETTY_NAME=[^(]+ \(?([^)]+?)\)", data)
  390. if release:
  391. self.facts['distribution_release'] = release.groups()[0]
  392. break
  393. elif name == 'Mandriva':
  394. data = get_file_content(path)
  395. if 'Mandriva' in data:
  396. version = re.search('DISTRIB_RELEASE="(.*)"', data)
  397. if version:
  398. self.facts['distribution_version'] = version.groups()[0]
  399. release = re.search('DISTRIB_CODENAME="(.*)"', data)
  400. if release:
  401. self.facts['distribution_release'] = release.groups()[0]
  402. self.facts['distribution'] = name
  403. break
  404. elif name == 'NA':
  405. data = get_file_content(path)
  406. for line in data.splitlines():
  407. distribution = re.search("^NAME=(.*)", line)
  408. if distribution:
  409. self.facts['distribution'] = distribution.group(1).strip('"')
  410. version = re.search("^VERSION=(.*)", line)
  411. if version:
  412. self.facts['distribution_version'] = version.group(1).strip('"')
  413. if self.facts['distribution'].lower() == 'coreos':
  414. data = get_file_content('/etc/coreos/update.conf')
  415. release = re.search("^GROUP=(.*)", data)
  416. if release:
  417. self.facts['distribution_release'] = release.group(1).strip('"')
  418. else:
  419. self.facts['distribution'] = name
  420. machine_id = get_file_content("/var/lib/dbus/machine-id") or get_file_content("/etc/machine-id")
  421. if machine_id:
  422. machine_id = machine_id.split('\n')[0]
  423. self.facts["machine_id"] = machine_id
  424. self.facts['os_family'] = self.facts['distribution']
  425. if self.facts['distribution'] in OS_FAMILY:
  426. self.facts['os_family'] = OS_FAMILY[self.facts['distribution']]
  427. def get_cmdline(self):
  428. data = get_file_content('/proc/cmdline')
  429. if data:
  430. self.facts['cmdline'] = {}
  431. try:
  432. for piece in shlex.split(data):
  433. item = piece.split('=', 1)
  434. if len(item) == 1:
  435. self.facts['cmdline'][item[0]] = True
  436. else:
  437. self.facts['cmdline'][item[0]] = item[1]
  438. except ValueError, e:
  439. pass
  440. def get_public_ssh_host_keys(self):
  441. dsa_filename = '/etc/ssh/ssh_host_dsa_key.pub'
  442. rsa_filename = '/etc/ssh/ssh_host_rsa_key.pub'
  443. ecdsa_filename = '/etc/ssh/ssh_host_ecdsa_key.pub'
  444. if self.facts['system'] == 'Darwin':
  445. dsa_filename = '/etc/ssh_host_dsa_key.pub'
  446. rsa_filename = '/etc/ssh_host_rsa_key.pub'
  447. ecdsa_filename = '/etc/ssh_host_ecdsa_key.pub'
  448. dsa = get_file_content(dsa_filename)
  449. rsa = get_file_content(rsa_filename)
  450. ecdsa = get_file_content(ecdsa_filename)
  451. if dsa is None:
  452. dsa = 'NA'
  453. else:
  454. self.facts['ssh_host_key_dsa_public'] = dsa.split()[1]
  455. if rsa is None:
  456. rsa = 'NA'
  457. else:
  458. self.facts['ssh_host_key_rsa_public'] = rsa.split()[1]
  459. if ecdsa is None:
  460. ecdsa = 'NA'
  461. else:
  462. self.facts['ssh_host_key_ecdsa_public'] = ecdsa.split()[1]
  463. def get_pkg_mgr_facts(self):
  464. self.facts['pkg_mgr'] = 'unknown'
  465. for pkg in Facts.PKG_MGRS:
  466. if os.path.exists(pkg['path']):
  467. self.facts['pkg_mgr'] = pkg['name']
  468. if self.facts['system'] == 'OpenBSD':
  469. self.facts['pkg_mgr'] = 'openbsd_pkg'
  470. def get_lsb_facts(self):
  471. lsb_path = module.get_bin_path('lsb_release')
  472. if lsb_path:
  473. rc, out, err = module.run_command([lsb_path, "-a"])
  474. if rc == 0:
  475. self.facts['lsb'] = {}
  476. for line in out.split('\n'):
  477. if len(line) < 1 or ':' not in line:
  478. continue
  479. value = line.split(':', 1)[1].strip()
  480. if 'LSB Version:' in line:
  481. self.facts['lsb']['release'] = value
  482. elif 'Distributor ID:' in line:
  483. self.facts['lsb']['id'] = value
  484. elif 'Description:' in line:
  485. self.facts['lsb']['description'] = value
  486. elif 'Release:' in line:
  487. self.facts['lsb']['release'] = value
  488. elif 'Codename:' in line:
  489. self.facts['lsb']['codename'] = value
  490. if 'lsb' in self.facts and 'release' in self.facts['lsb']:
  491. self.facts['lsb']['major_release'] = self.facts['lsb']['release'].split('.')[0]
  492. elif lsb_path is None and os.path.exists('/etc/lsb-release'):
  493. self.facts['lsb'] = {}
  494. for line in get_file_lines('/etc/lsb-release'):
  495. value = line.split('=',1)[1].strip()
  496. if 'DISTRIB_ID' in line:
  497. self.facts['lsb']['id'] = value
  498. elif 'DISTRIB_RELEASE' in line:
  499. self.facts['lsb']['release'] = value
  500. elif 'DISTRIB_DESCRIPTION' in line:
  501. self.facts['lsb']['description'] = value
  502. elif 'DISTRIB_CODENAME' in line:
  503. self.facts['lsb']['codename'] = value
  504. else:
  505. return self.facts
  506. if 'lsb' in self.facts and 'release' in self.facts['lsb']:
  507. self.facts['lsb']['major_release'] = self.facts['lsb']['release'].split('.')[0]
  508. def get_selinux_facts(self):
  509. if not HAVE_SELINUX:
  510. self.facts['selinux'] = False
  511. return
  512. self.facts['selinux'] = {}
  513. if not selinux.is_selinux_enabled():
  514. self.facts['selinux']['status'] = 'disabled'
  515. else:
  516. self.facts['selinux']['status'] = 'enabled'
  517. try:
  518. self.facts['selinux']['policyvers'] = selinux.security_policyvers()
  519. except OSError, e:
  520. self.facts['selinux']['policyvers'] = 'unknown'
  521. try:
  522. (rc, configmode) = selinux.selinux_getenforcemode()
  523. if rc == 0:
  524. self.facts['selinux']['config_mode'] = Facts.SELINUX_MODE_DICT.get(configmode, 'unknown')
  525. else:
  526. self.facts['selinux']['config_mode'] = 'unknown'
  527. except OSError, e:
  528. self.facts['selinux']['config_mode'] = 'unknown'
  529. try:
  530. mode = selinux.security_getenforce()
  531. self.facts['selinux']['mode'] = Facts.SELINUX_MODE_DICT.get(mode, 'unknown')
  532. except OSError, e:
  533. self.facts['selinux']['mode'] = 'unknown'
  534. try:
  535. (rc, policytype) = selinux.selinux_getpolicytype()
  536. if rc == 0:
  537. self.facts['selinux']['type'] = policytype
  538. else:
  539. self.facts['selinux']['type'] = 'unknown'
  540. except OSError, e:
  541. self.facts['selinux']['type'] = 'unknown'
  542. def get_fips_facts(self):
  543. self.facts['fips'] = False
  544. data = get_file_content('/proc/sys/crypto/fips_enabled')
  545. if data and data == '1':
  546. self.facts['fips'] = True
  547. def get_date_time_facts(self):
  548. self.facts['date_time'] = {}
  549. now = datetime.datetime.now()
  550. self.facts['date_time']['year'] = now.strftime('%Y')
  551. self.facts['date_time']['month'] = now.strftime('%m')
  552. self.facts['date_time']['weekday'] = now.strftime('%A')
  553. self.facts['date_time']['day'] = now.strftime('%d')
  554. self.facts['date_time']['hour'] = now.strftime('%H')
  555. self.facts['date_time']['minute'] = now.strftime('%M')
  556. self.facts['date_time']['second'] = now.strftime('%S')
  557. self.facts['date_time']['epoch'] = now.strftime('%s')
  558. if self.facts['date_time']['epoch'] == '' or self.facts['date_time']['epoch'][0] == '%':
  559. self.facts['date_time']['epoch'] = str(int(time.time()))
  560. self.facts['date_time']['date'] = now.strftime('%Y-%m-%d')
  561. self.facts['date_time']['time'] = now.strftime('%H:%M:%S')
  562. self.facts['date_time']['iso8601_micro'] = now.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")
  563. self.facts['date_time']['iso8601'] = now.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
  564. self.facts['date_time']['tz'] = time.strftime("%Z")
  565. self.facts['date_time']['tz_offset'] = time.strftime("%z")
  566. # User
  567. def get_user_facts(self):
  568. self.facts['user_id'] = getpass.getuser()
  569. pwent = pwd.getpwnam(getpass.getuser())
  570. self.facts['user_uid'] = pwent.pw_uid
  571. self.facts['user_gid'] = pwent.pw_gid
  572. self.facts['user_gecos'] = pwent.pw_gecos
  573. self.facts['user_dir'] = pwent.pw_dir
  574. self.facts['user_shell'] = pwent.pw_shell
  575. def get_env_facts(self):
  576. self.facts['env'] = {}
  577. for k,v in os.environ.iteritems():
  578. self.facts['env'][k] = v
  579. class Hardware(Facts):
  580. """
  581. This is a generic Hardware subclass of Facts. This should be further
  582. subclassed to implement per platform. If you subclass this, it
  583. should define:
  584. - memfree_mb
  585. - memtotal_mb
  586. - swapfree_mb
  587. - swaptotal_mb
  588. - processor (a list)
  589. - processor_cores
  590. - processor_count
  591. All subclasses MUST define platform.
  592. """
  593. platform = 'Generic'
  594. def __new__(cls, *arguments, **keyword):
  595. subclass = cls
  596. for sc in Hardware.__subclasses__():
  597. if sc.platform == platform.system():
  598. subclass = sc
  599. return super(cls, subclass).__new__(subclass, *arguments, **keyword)
  600. def __init__(self):
  601. Facts.__init__(self)
  602. def populate(self):
  603. return self.facts
  604. class LinuxHardware(Hardware):
  605. """
  606. Linux-specific subclass of Hardware. Defines memory and CPU facts:
  607. - memfree_mb
  608. - memtotal_mb
  609. - swapfree_mb
  610. - swaptotal_mb
  611. - processor (a list)
  612. - processor_cores
  613. - processor_count
  614. In addition, it also defines number of DMI facts and device facts.
  615. """
  616. platform = 'Linux'
  617. # Originally only had these four as toplevelfacts
  618. ORIGINAL_MEMORY_FACTS = frozenset(('MemTotal', 'SwapTotal', 'MemFree', 'SwapFree'))
  619. # Now we have all of these in a dict structure
  620. MEMORY_FACTS = ORIGINAL_MEMORY_FACTS.union(('Buffers', 'Cached', 'SwapCached'))
  621. def __init__(self):
  622. Hardware.__init__(self)
  623. def populate(self):
  624. self.get_cpu_facts()
  625. self.get_memory_facts()
  626. self.get_dmi_facts()
  627. self.get_device_facts()
  628. self.get_uptime_facts()
  629. try:
  630. self.get_mount_facts()
  631. except TimeoutError:
  632. pass
  633. return self.facts
  634. def get_memory_facts(self):
  635. if not os.access("/proc/meminfo", os.R_OK):
  636. return
  637. memstats = {}
  638. for line in get_file_lines("/proc/meminfo"):
  639. data = line.split(":", 1)
  640. key = data[0]
  641. if key in self.ORIGINAL_MEMORY_FACTS:
  642. val = data[1].strip().split(' ')[0]
  643. self.facts["%s_mb" % key.lower()] = long(val) / 1024
  644. if key in self.MEMORY_FACTS:
  645. val = data[1].strip().split(' ')[0]
  646. memstats[key.lower()] = long(val) / 1024
  647. if None not in (memstats.get('memtotal'), memstats.get('memfree')):
  648. memstats['real:used'] = memstats['memtotal'] - memstats['memfree']
  649. if None not in (memstats.get('cached'), memstats.get('memfree'), memstats.get('buffers')):
  650. memstats['nocache:free'] = memstats['cached'] + memstats['memfree'] + memstats['buffers']
  651. if None not in (memstats.get('memtotal'), memstats.get('nocache:free')):
  652. memstats['nocache:used'] = memstats['memtotal'] - memstats['nocache:free']
  653. if None not in (memstats.get('swaptotal'), memstats.get('swapfree')):
  654. memstats['swap:used'] = memstats['swaptotal'] - memstats['swapfree']
  655. self.facts['memory_mb'] = {
  656. 'real' : {
  657. 'total': memstats.get('memtotal'),
  658. 'used': memstats.get('real:used'),
  659. 'free': memstats.get('memfree'),
  660. },
  661. 'nocache' : {
  662. 'free': memstats.get('nocache:free'),
  663. 'used': memstats.get('nocache:used'),
  664. },
  665. 'swap' : {
  666. 'total': memstats.get('swaptotal'),
  667. 'free': memstats.get('swapfree'),
  668. 'used': memstats.get('swap:used'),
  669. 'cached': memstats.get('swapcached'),
  670. },
  671. }
  672. def get_cpu_facts(self):
  673. i = 0
  674. vendor_id_occurrence = 0
  675. model_name_occurrence = 0
  676. physid = 0
  677. coreid = 0
  678. sockets = {}
  679. cores = {}
  680. xen = False
  681. xen_paravirt = False
  682. try:
  683. if os.path.exists('/proc/xen'):
  684. xen = True
  685. else:
  686. for line in get_file_lines('/sys/hypervisor/type'):
  687. if line.strip() == 'xen':
  688. xen = True
  689. # Only interested in the first line
  690. break
  691. except IOError:
  692. pass
  693. if not os.access("/proc/cpuinfo", os.R_OK):
  694. return
  695. self.facts['processor'] = []
  696. for line in get_file_lines('/proc/cpuinfo'):
  697. data = line.split(":", 1)
  698. key = data[0].strip()
  699. if xen:
  700. if key == 'flags':
  701. # Check for vme cpu flag, Xen paravirt does not expose this.
  702. # Need to detect Xen paravirt because it exposes cpuinfo
  703. # differently than Xen HVM or KVM and causes reporting of
  704. # only a single cpu core.
  705. if 'vme' not in data:
  706. xen_paravirt = True
  707. # model name is for Intel arch, Processor (mind the uppercase P)
  708. # works for some ARM devices, like the Sheevaplug.
  709. if key == 'model name' or key == 'Processor' or key == 'vendor_id':
  710. if 'processor' not in self.facts:
  711. self.facts['processor'] = []
  712. self.facts['processor'].append(data[1].strip())
  713. if key == 'vendor_id':
  714. vendor_id_occurrence += 1
  715. if key == 'model name':
  716. model_name_occurrence += 1
  717. i += 1
  718. elif key == 'physical id':
  719. physid = data[1].strip()
  720. if physid not in sockets:
  721. sockets[physid] = 1
  722. elif key == 'core id':
  723. coreid = data[1].strip()
  724. if coreid not in sockets:
  725. cores[coreid] = 1
  726. elif key == 'cpu cores':
  727. sockets[physid] = int(data[1].strip())
  728. elif key == 'siblings':
  729. cores[coreid] = int(data[1].strip())
  730. elif key == '# processors':
  731. self.facts['processor_cores'] = int(data[1].strip())
  732. if vendor_id_occurrence == model_name_occurrence:
  733. i = vendor_id_occurrence
  734. if self.facts['architecture'] != 's390x':
  735. if xen_paravirt:
  736. self.facts['processor_count'] = i
  737. self.facts['processor_cores'] = i
  738. self.facts['processor_threads_per_core'] = 1
  739. self.facts['processor_vcpus'] = i
  740. else:
  741. self.facts['processor_count'] = sockets and len(sockets) or i
  742. self.facts['processor_cores'] = sockets.values() and sockets.values()[0] or 1
  743. self.facts['processor_threads_per_core'] = ((cores.values() and
  744. cores.values()[0] or 1) / self.facts['processor_cores'])
  745. self.facts['processor_vcpus'] = (self.facts['processor_threads_per_core'] *
  746. self.facts['processor_count'] * self.facts['processor_cores'])
  747. def get_dmi_facts(self):
  748. ''' learn dmi facts from system
  749. Try /sys first for dmi related facts.
  750. If that is not available, fall back to dmidecode executable '''
  751. if os.path.exists('/sys/devices/virtual/dmi/id/product_name'):
  752. # Use kernel DMI info, if available
  753. # DMI SPEC -- http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.0.pdf
  754. FORM_FACTOR = [ "Unknown", "Other", "Unknown", "Desktop",
  755. "Low Profile Desktop", "Pizza Box", "Mini Tower", "Tower",
  756. "Portable", "Laptop", "Notebook", "Hand Held", "Docking Station",
  757. "All In One", "Sub Notebook", "Space-saving", "Lunch Box",
  758. "Main Server Chassis", "Expansion Chassis", "Sub Chassis",
  759. "Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis",
  760. "Rack Mount Chassis", "Sealed-case PC", "Multi-system",
  761. "CompactPCI", "AdvancedTCA", "Blade" ]
  762. DMI_DICT = {
  763. 'bios_date': '/sys/devices/virtual/dmi/id/bios_date',
  764. 'bios_version': '/sys/devices/virtual/dmi/id/bios_version',
  765. 'form_factor': '/sys/devices/virtual/dmi/id/chassis_type',
  766. 'product_name': '/sys/devices/virtual/dmi/id/product_name',
  767. 'product_serial': '/sys/devices/virtual/dmi/id/product_serial',
  768. 'product_uuid': '/sys/devices/virtual/dmi/id/product_uuid',
  769. 'product_version': '/sys/devices/virtual/dmi/id/product_version',
  770. 'system_vendor': '/sys/devices/virtual/dmi/id/sys_vendor'
  771. }
  772. for (key,path) in DMI_DICT.items():
  773. data = get_file_content(path)
  774. if data is not None:
  775. if key == 'form_factor':
  776. try:
  777. self.facts['form_factor'] = FORM_FACTOR[int(data)]
  778. except IndexError, e:
  779. self.facts['form_factor'] = 'unknown (%s)' % data
  780. else:
  781. self.facts[key] = data
  782. else:
  783. self.facts[key] = 'NA'
  784. else:
  785. # Fall back to using dmidecode, if available
  786. dmi_bin = module.get_bin_path('dmidecode')
  787. DMI_DICT = {
  788. 'bios_date': 'bios-release-date',
  789. 'bios_version': 'bios-version',
  790. 'form_factor': 'chassis-type',
  791. 'product_name': 'system-product-name',
  792. 'product_serial': 'system-serial-number',
  793. 'product_uuid': 'system-uuid',
  794. 'product_version': 'system-version',
  795. 'system_vendor': 'system-manufacturer'
  796. }
  797. for (k, v) in DMI_DICT.items():
  798. if dmi_bin is not None:
  799. (rc, out, err) = module.run_command('%s -s %s' % (dmi_bin, v))
  800. if rc == 0:
  801. # Strip out commented lines (specific dmidecode output)
  802. thisvalue = ''.join([ line for line in out.split('\n') if not line.startswith('#') ])
  803. try:
  804. json.dumps(thisvalue)
  805. except UnicodeDecodeError:
  806. thisvalue = "NA"
  807. self.facts[k] = thisvalue
  808. else:
  809. self.facts[k] = 'NA'
  810. else:
  811. self.facts[k] = 'NA'
  812. @timeout(10)
  813. def get_mount_facts(self):
  814. self.facts['mounts'] = []
  815. mtab = get_file_content('/etc/mtab', '')
  816. for line in mtab.split('\n'):
  817. if line.startswith('/'):
  818. fields = line.rstrip('\n').split()
  819. if(fields[2] != 'none'):
  820. size_total = None
  821. size_available = None
  822. try:
  823. statvfs_result = os.statvfs(fields[1])
  824. size_total = statvfs_result.f_bsize * statvfs_result.f_blocks
  825. size_available = statvfs_result.f_bsize * (statvfs_result.f_bavail)
  826. except OSError, e:
  827. continue
  828. uuid = 'NA'
  829. lsblkPath = module.get_bin_path("lsblk")
  830. if lsblkPath:
  831. rc, out, err = module.run_command("%s -ln --output UUID %s" % (lsblkPath, fields[0]), use_unsafe_shell=True)
  832. if rc == 0:
  833. uuid = out.strip()
  834. self.facts['mounts'].append(
  835. {'mount': fields[1],
  836. 'device':fields[0],
  837. 'fstype': fields[2],
  838. 'options': fields[3],
  839. # statvfs data
  840. 'size_total': size_total,
  841. 'size_available': size_available,
  842. 'uuid': uuid,
  843. })
  844. def get_device_facts(self):
  845. self.facts['devices'] = {}
  846. lspci = module.get_bin_path('lspci')
  847. if lspci:
  848. rc, pcidata, err = module.run_command([lspci, '-D'])
  849. else:
  850. pcidata = None
  851. try:
  852. block_devs = os.listdir("/sys/block")
  853. except OSError:
  854. return
  855. for block in block_devs:
  856. virtual = 1
  857. sysfs_no_links = 0
  858. try:
  859. path = os.readlink(os.path.join("/sys/block/", block))
  860. except OSError, e:
  861. if e.errno == errno.EINVAL:
  862. path = block
  863. sysfs_no_links = 1
  864. else:
  865. continue
  866. if "virtual" in path:
  867. continue
  868. sysdir = os.path.join("/sys/block", path)
  869. if sysfs_no_links == 1:
  870. for folder in os.listdir(sysdir):
  871. if "device" in folder:
  872. virtual = 0
  873. break
  874. if virtual:
  875. continue
  876. d = {}
  877. diskname = os.path.basename(sysdir)
  878. for key in ['vendor', 'model']:
  879. d[key] = get_file_content(sysdir + "/device/" + key)
  880. for key,test in [ ('removable','/removable'), \
  881. ('support_discard','/queue/discard_granularity'),
  882. ]:
  883. d[key] = get_file_content(sysdir + test)
  884. d['partitions'] = {}
  885. for folder in os.listdir(sysdir):
  886. m = re.search("(" + diskname + "\d+)", folder)
  887. if m:
  888. part = {}
  889. partname = m.group(1)
  890. part_sysdir = sysdir + "/" + partname
  891. part['start'] = get_file_content(part_sysdir + "/start",0)
  892. part['sectors'] = get_file_content(part_sysdir + "/size",0)
  893. part['sectorsize'] = get_file_content(part_sysdir + "/queue/physical_block_size")
  894. if not part['sectorsize']:
  895. part['sectorsize'] = get_file_content(part_sysdir + "/queue/hw_sector_size",512)
  896. part['size'] = module.pretty_bytes((float(part['sectors']) * float(part['sectorsize'])))
  897. d['partitions'][partname] = part
  898. d['rotational'] = get_file_content(sysdir + "/queue/rotational")
  899. d['scheduler_mode'] = ""
  900. scheduler = get_file_content(sysdir + "/queue/scheduler")
  901. if scheduler is not None:
  902. m = re.match(".*?(\[(.*)\])", scheduler)
  903. if m:
  904. d['scheduler_mode'] = m.group(2)
  905. d['sectors'] = get_file_content(sysdir + "/size")
  906. if not d['sectors']:
  907. d['sectors'] = 0
  908. d['sectorsize'] = get_file_content(sysdir + "/queue/physical_block_size")
  909. if not d['sectorsize']:
  910. d['sectorsize'] = get_file_content(sysdir + "/queue/hw_sector_size",512)
  911. d['size'] = module.pretty_bytes(float(d['sectors']) * float(d['sectorsize']))
  912. d['host'] = ""
  913. # domains are numbered (0 to ffff), bus (0 to ff), slot (0 to 1f), and function (0 to 7).
  914. m = re.match(".+/([a-f0-9]{4}:[a-f0-9]{2}:[0|1][a-f0-9]\.[0-7])/", sysdir)
  915. if m and pcidata:
  916. pciid = m.group(1)
  917. did = re.escape(pciid)
  918. m = re.search("^" + did + "\s(.*)$", pcidata, re.MULTILINE)
  919. d['host'] = m.group(1)
  920. d['holders'] = []
  921. if os.path.isdir(sysdir + "/holders"):
  922. for folder in os.listdir(sysdir + "/holders"):
  923. if not folder.startswith("dm-"):
  924. continue
  925. name = get_file_content(sysdir + "/holders/" + folder + "/dm/name")
  926. if name:
  927. d['holders'].append(name)
  928. else:
  929. d['holders'].append(folder)
  930. self.facts['devices'][diskname] = d
  931. def get_uptime_facts(self):
  932. uptime_seconds_string = get_file_content('/proc/uptime').split(' ')[0]
  933. self.facts['uptime_seconds'] = int(float(uptime_seconds_string))
  934. class SunOSHardware(Hardware):
  935. """
  936. In addition to the generic memory and cpu facts, this also sets
  937. swap_reserved_mb and swap_allocated_mb that is available from *swap -s*.
  938. """
  939. platform = 'SunOS'
  940. def __init__(self):
  941. Hardware.__init__(self)
  942. def populate(self):
  943. self.get_cpu_facts()
  944. self.get_memory_facts()
  945. try:
  946. self.get_mount_facts()
  947. except TimeoutError:
  948. pass
  949. return self.facts
  950. def get_cpu_facts(self):
  951. physid = 0
  952. sockets = {}
  953. rc, out, err = module.run_command("/usr/bin/kstat cpu_info")
  954. self.facts['processor'] = []
  955. for line in out.split('\n'):
  956. if len(line) < 1:
  957. continue
  958. data = line.split(None, 1)
  959. key = data[0].strip()
  960. # "brand" works on Solaris 10 & 11. "implementation" for Solaris 9.
  961. if key == 'module:':
  962. brand = ''
  963. elif key == 'brand':
  964. brand = data[1].strip()
  965. elif key == 'clock_MHz':
  966. clock_mhz = data[1].strip()
  967. elif key == 'implementation':
  968. processor = brand or data[1].strip()
  969. # Add clock speed to description for SPARC CPU
  970. if self.facts['machine'] != 'i86pc':
  971. processor += " @ " + clock_mhz + "MHz"
  972. if 'processor' not in self.facts:
  973. self.facts['processor'] = []
  974. self.facts['processor'].append(processor)
  975. elif key == 'chip_id':
  976. physid = data[1].strip()
  977. if physid not in sockets:
  978. sockets[physid] = 1
  979. else:
  980. sockets[physid] += 1
  981. # Counting cores on Solaris can be complicated.
  982. # https://blogs.oracle.com/mandalika/entry/solaris_show_me_the_cpu
  983. # Treat 'processor_count' as physical sockets and 'processor_cores' as
  984. # virtual CPUs visisble to Solaris. Not a true count of cores for modern SPARC as
  985. # these processors have: sockets -> cores -> threads/virtual CPU.
  986. if len(sockets) > 0:
  987. self.facts['processor_count'] = len(sockets)
  988. self.facts['processor_cores'] = reduce(lambda x, y: x + y, sockets.values())
  989. else:
  990. self.facts['processor_cores'] = 'NA'
  991. self.facts['processor_count'] = len(self.facts['processor'])
  992. def get_memory_facts(self):
  993. rc, out, err = module.run_command(["/usr/sbin/prtconf"])
  994. for line in out.split('\n'):
  995. if 'Memory size' in line:
  996. self.facts['memtotal_mb'] = line.split()[2]
  997. rc, out, err = module.run_command("/usr/sbin/swap -s")