openshift_facts.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # vim: expandtab:tabstop=4:shiftwidth=4
  4. # disable pylint checks
  5. # temporarily disabled until items can be addressed:
  6. # fixme - until all TODO comments have been addressed
  7. # pylint:disable=fixme
  8. """Ansible module for retrieving and setting openshift related facts"""
  9. DOCUMENTATION = '''
  10. ---
  11. module: openshift_facts
  12. short_description: OpenShift Facts
  13. author: Jason DeTiberus
  14. requirements: [ ]
  15. '''
  16. EXAMPLES = '''
  17. '''
  18. import ConfigParser
  19. import copy
  20. def hostname_valid(hostname):
  21. """ Test if specified hostname should be considered valid
  22. Args:
  23. hostname (str): hostname to test
  24. Returns:
  25. bool: True if valid, otherwise False
  26. """
  27. if (not hostname or
  28. hostname.startswith('localhost') or
  29. hostname.endswith('localdomain') or
  30. len(hostname.split('.')) < 2):
  31. return False
  32. return True
  33. def choose_hostname(hostnames=None, fallback=''):
  34. """ Choose a hostname from the provided hostnames
  35. Given a list of hostnames and a fallback value, choose a hostname to
  36. use. This function will prefer fqdns if they exist (excluding any that
  37. begin with localhost or end with localdomain) over ip addresses.
  38. Args:
  39. hostnames (list): list of hostnames
  40. fallback (str): default value to set if hostnames does not contain
  41. a valid hostname
  42. Returns:
  43. str: chosen hostname
  44. """
  45. hostname = fallback
  46. if hostnames is None:
  47. return hostname
  48. ip_regex = r'\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z'
  49. ips = [i for i in hostnames
  50. if (i is not None and isinstance(i, basestring)
  51. and re.match(ip_regex, i))]
  52. hosts = [i for i in hostnames
  53. if i is not None and i != '' and i not in ips]
  54. for host_list in (hosts, ips):
  55. for host in host_list:
  56. if hostname_valid(host):
  57. return host
  58. return hostname
  59. def query_metadata(metadata_url, headers=None, expect_json=False):
  60. """ Return metadata from the provided metadata_url
  61. Args:
  62. metadata_url (str): metadata url
  63. headers (dict): headers to set for metadata request
  64. expect_json (bool): does the metadata_url return json
  65. Returns:
  66. dict or list: metadata request result
  67. """
  68. result, info = fetch_url(module, metadata_url, headers=headers)
  69. if info['status'] != 200:
  70. raise OpenShiftFactsMetadataUnavailableError("Metadata unavailable")
  71. if expect_json:
  72. return module.from_json(result.read())
  73. else:
  74. return [line.strip() for line in result.readlines()]
  75. def walk_metadata(metadata_url, headers=None, expect_json=False):
  76. """ Walk the metadata tree and return a dictionary of the entire tree
  77. Args:
  78. metadata_url (str): metadata url
  79. headers (dict): headers to set for metadata request
  80. expect_json (bool): does the metadata_url return json
  81. Returns:
  82. dict: the result of walking the metadata tree
  83. """
  84. metadata = dict()
  85. for line in query_metadata(metadata_url, headers, expect_json):
  86. if line.endswith('/') and not line == 'public-keys/':
  87. key = line[:-1]
  88. metadata[key] = walk_metadata(metadata_url + line,
  89. headers, expect_json)
  90. else:
  91. results = query_metadata(metadata_url + line, headers,
  92. expect_json)
  93. if len(results) == 1:
  94. # disable pylint maybe-no-member because overloaded use of
  95. # the module name causes pylint to not detect that results
  96. # is an array or hash
  97. # pylint: disable=maybe-no-member
  98. metadata[line] = results.pop()
  99. else:
  100. metadata[line] = results
  101. return metadata
  102. def get_provider_metadata(metadata_url, supports_recursive=False,
  103. headers=None, expect_json=False):
  104. """ Retrieve the provider metadata
  105. Args:
  106. metadata_url (str): metadata url
  107. supports_recursive (bool): does the provider metadata api support
  108. recursion
  109. headers (dict): headers to set for metadata request
  110. expect_json (bool): does the metadata_url return json
  111. Returns:
  112. dict: the provider metadata
  113. """
  114. try:
  115. if supports_recursive:
  116. metadata = query_metadata(metadata_url, headers,
  117. expect_json)
  118. else:
  119. metadata = walk_metadata(metadata_url, headers,
  120. expect_json)
  121. except OpenShiftFactsMetadataUnavailableError:
  122. metadata = None
  123. return metadata
  124. def normalize_gce_facts(metadata, facts):
  125. """ Normalize gce facts
  126. Args:
  127. metadata (dict): provider metadata
  128. facts (dict): facts to update
  129. Returns:
  130. dict: the result of adding the normalized metadata to the provided
  131. facts dict
  132. """
  133. for interface in metadata['instance']['networkInterfaces']:
  134. int_info = dict(ips=[interface['ip']], network_type='gce')
  135. int_info['public_ips'] = [ac['externalIp'] for ac
  136. in interface['accessConfigs']]
  137. int_info['public_ips'].extend(interface['forwardedIps'])
  138. _, _, network_id = interface['network'].rpartition('/')
  139. int_info['network_id'] = network_id
  140. facts['network']['interfaces'].append(int_info)
  141. _, _, zone = metadata['instance']['zone'].rpartition('/')
  142. facts['zone'] = zone
  143. facts['external_id'] = metadata['instance']['id']
  144. # Default to no sdn for GCE deployments
  145. facts['use_openshift_sdn'] = False
  146. # GCE currently only supports a single interface
  147. facts['network']['ip'] = facts['network']['interfaces'][0]['ips'][0]
  148. pub_ip = facts['network']['interfaces'][0]['public_ips'][0]
  149. facts['network']['public_ip'] = pub_ip
  150. facts['network']['hostname'] = metadata['instance']['hostname']
  151. # TODO: attempt to resolve public_hostname
  152. facts['network']['public_hostname'] = facts['network']['public_ip']
  153. return facts
  154. def normalize_aws_facts(metadata, facts):
  155. """ Normalize aws facts
  156. Args:
  157. metadata (dict): provider metadata
  158. facts (dict): facts to update
  159. Returns:
  160. dict: the result of adding the normalized metadata to the provided
  161. facts dict
  162. """
  163. for interface in sorted(
  164. metadata['network']['interfaces']['macs'].values(),
  165. key=lambda x: x['device-number']
  166. ):
  167. int_info = dict()
  168. var_map = {'ips': 'local-ipv4s', 'public_ips': 'public-ipv4s'}
  169. for ips_var, int_var in var_map.iteritems():
  170. ips = interface.get(int_var)
  171. if isinstance(ips, basestring):
  172. int_info[ips_var] = [ips]
  173. else:
  174. int_info[ips_var] = ips
  175. if 'vpc-id' in interface:
  176. int_info['network_type'] = 'vpc'
  177. else:
  178. int_info['network_type'] = 'classic'
  179. if int_info['network_type'] == 'vpc':
  180. int_info['network_id'] = interface['subnet-id']
  181. else:
  182. int_info['network_id'] = None
  183. facts['network']['interfaces'].append(int_info)
  184. facts['zone'] = metadata['placement']['availability-zone']
  185. facts['external_id'] = metadata['instance-id']
  186. # TODO: actually attempt to determine default local and public ips
  187. # by using the ansible default ip fact and the ipv4-associations
  188. # from the ec2 metadata
  189. facts['network']['ip'] = metadata.get('local-ipv4')
  190. facts['network']['public_ip'] = metadata.get('public-ipv4')
  191. # TODO: verify that local hostname makes sense and is resolvable
  192. facts['network']['hostname'] = metadata.get('local-hostname')
  193. # TODO: verify that public hostname makes sense and is resolvable
  194. facts['network']['public_hostname'] = metadata.get('public-hostname')
  195. return facts
  196. def normalize_openstack_facts(metadata, facts):
  197. """ Normalize openstack facts
  198. Args:
  199. metadata (dict): provider metadata
  200. facts (dict): facts to update
  201. Returns:
  202. dict: the result of adding the normalized metadata to the provided
  203. facts dict
  204. """
  205. # openstack ec2 compat api does not support network interfaces and
  206. # the version tested on did not include the info in the openstack
  207. # metadata api, should be updated if neutron exposes this.
  208. facts['zone'] = metadata['availability_zone']
  209. facts['external_id'] = metadata['uuid']
  210. facts['network']['ip'] = metadata['ec2_compat']['local-ipv4']
  211. facts['network']['public_ip'] = metadata['ec2_compat']['public-ipv4']
  212. # TODO: verify local hostname makes sense and is resolvable
  213. facts['network']['hostname'] = metadata['hostname']
  214. # TODO: verify that public hostname makes sense and is resolvable
  215. pub_h = metadata['ec2_compat']['public-hostname']
  216. facts['network']['public_hostname'] = pub_h
  217. return facts
  218. def normalize_provider_facts(provider, metadata):
  219. """ Normalize provider facts
  220. Args:
  221. provider (str): host provider
  222. metadata (dict): provider metadata
  223. Returns:
  224. dict: the normalized provider facts
  225. """
  226. if provider is None or metadata is None:
  227. return {}
  228. # TODO: test for ipv6_enabled where possible (gce, aws do not support)
  229. # and configure ipv6 facts if available
  230. # TODO: add support for setting user_data if available
  231. facts = dict(name=provider, metadata=metadata,
  232. network=dict(interfaces=[], ipv6_enabled=False))
  233. if provider == 'gce':
  234. facts = normalize_gce_facts(metadata, facts)
  235. elif provider == 'ec2':
  236. facts = normalize_aws_facts(metadata, facts)
  237. elif provider == 'openstack':
  238. facts = normalize_openstack_facts(metadata, facts)
  239. return facts
  240. def set_fluentd_facts_if_unset(facts):
  241. """ Set fluentd facts if not already present in facts dict
  242. Args:
  243. facts (dict): existing facts
  244. Returns:
  245. dict: the facts dict updated with the generated fluentd facts if
  246. missing
  247. """
  248. if 'common' in facts:
  249. deployment_type = facts['common']['deployment_type']
  250. if 'use_fluentd' not in facts['common']:
  251. use_fluentd = True if deployment_type == 'online' else False
  252. facts['common']['use_fluentd'] = use_fluentd
  253. return facts
  254. def set_url_facts_if_unset(facts):
  255. """ Set url facts if not already present in facts dict
  256. Args:
  257. facts (dict): existing facts
  258. Returns:
  259. dict: the facts dict updated with the generated url facts if they
  260. were not already present
  261. """
  262. if 'master' in facts:
  263. for (url_var, use_ssl, port, default) in [
  264. ('api_url',
  265. facts['master']['api_use_ssl'],
  266. facts['master']['api_port'],
  267. facts['common']['hostname']),
  268. ('public_api_url',
  269. facts['master']['api_use_ssl'],
  270. facts['master']['api_port'],
  271. facts['common']['public_hostname']),
  272. ('console_url',
  273. facts['master']['console_use_ssl'],
  274. facts['master']['console_port'],
  275. facts['common']['hostname']),
  276. ('public_console_url' 'console_use_ssl',
  277. facts['master']['console_use_ssl'],
  278. facts['master']['console_port'],
  279. facts['common']['public_hostname'])]:
  280. if url_var not in facts['master']:
  281. scheme = 'https' if use_ssl else 'http'
  282. netloc = default
  283. if ((scheme == 'https' and port != '443')
  284. or (scheme == 'http' and port != '80')):
  285. netloc = "%s:%s" % (netloc, port)
  286. facts['master'][url_var] = urlparse.urlunparse(
  287. (scheme, netloc, '', '', '', '')
  288. )
  289. return facts
  290. def get_current_config(facts):
  291. """ Get current openshift config
  292. Args:
  293. facts (dict): existing facts
  294. Returns:
  295. dict: the facts dict updated with the current openshift config
  296. """
  297. current_config = dict()
  298. roles = [role for role in facts if role not in ['common', 'provider']]
  299. for role in roles:
  300. if 'roles' in current_config:
  301. current_config['roles'].append(role)
  302. else:
  303. current_config['roles'] = [role]
  304. # TODO: parse the /etc/sysconfig/openshift-{master,node} config to
  305. # determine the location of files.
  306. # Query kubeconfig settings
  307. kubeconfig_dir = '/var/lib/openshift/openshift.local.certificates'
  308. if role == 'node':
  309. kubeconfig_dir = os.path.join(
  310. kubeconfig_dir, "node-%s" % facts['common']['hostname']
  311. )
  312. kubeconfig_path = os.path.join(kubeconfig_dir, '.kubeconfig')
  313. if (os.path.isfile('/usr/bin/openshift')
  314. and os.path.isfile(kubeconfig_path)):
  315. try:
  316. _, output, _ = module.run_command(
  317. ["/usr/bin/openshift", "ex", "config", "view", "-o",
  318. "json", "--kubeconfig=%s" % kubeconfig_path],
  319. check_rc=False
  320. )
  321. config = json.loads(output)
  322. cad = 'certificate-authority-data'
  323. try:
  324. for cluster in config['clusters']:
  325. config['clusters'][cluster][cad] = 'masked'
  326. except KeyError:
  327. pass
  328. try:
  329. for user in config['users']:
  330. config['users'][user][cad] = 'masked'
  331. config['users'][user]['client-key-data'] = 'masked'
  332. except KeyError:
  333. pass
  334. current_config['kubeconfig'] = config
  335. # override pylint broad-except warning, since we do not want
  336. # to bubble up any exceptions if openshift ex config view
  337. # fails
  338. # pylint: disable=broad-except
  339. except Exception:
  340. pass
  341. return current_config
  342. def apply_provider_facts(facts, provider_facts, roles):
  343. """ Apply provider facts to supplied facts dict
  344. Args:
  345. facts (dict): facts dict to update
  346. provider_facts (dict): provider facts to apply
  347. roles: host roles
  348. Returns:
  349. dict: the merged facts
  350. """
  351. if not provider_facts:
  352. return facts
  353. use_openshift_sdn = provider_facts.get('use_openshift_sdn')
  354. if isinstance(use_openshift_sdn, bool):
  355. facts['common']['use_openshift_sdn'] = use_openshift_sdn
  356. common_vars = [('hostname', 'ip'), ('public_hostname', 'public_ip')]
  357. for h_var, ip_var in common_vars:
  358. ip_value = provider_facts['network'].get(ip_var)
  359. if ip_value:
  360. facts['common'][ip_var] = ip_value
  361. facts['common'][h_var] = choose_hostname(
  362. [provider_facts['network'].get(h_var)],
  363. facts['common'][ip_var]
  364. )
  365. if 'node' in roles:
  366. ext_id = provider_facts.get('external_id')
  367. if ext_id:
  368. facts['node']['external_id'] = ext_id
  369. facts['provider'] = provider_facts
  370. return facts
  371. def merge_facts(orig, new):
  372. """ Recursively merge facts dicts
  373. Args:
  374. orig (dict): existing facts
  375. new (dict): facts to update
  376. Returns:
  377. dict: the merged facts
  378. """
  379. facts = dict()
  380. for key, value in orig.iteritems():
  381. if key in new:
  382. if isinstance(value, dict):
  383. facts[key] = merge_facts(value, new[key])
  384. else:
  385. facts[key] = copy.copy(new[key])
  386. else:
  387. facts[key] = copy.deepcopy(value)
  388. new_keys = set(new.keys()) - set(orig.keys())
  389. for key in new_keys:
  390. facts[key] = copy.deepcopy(new[key])
  391. return facts
  392. def save_local_facts(filename, facts):
  393. """ Save local facts
  394. Args:
  395. filename (str): local facts file
  396. facts (dict): facts to set
  397. """
  398. try:
  399. fact_dir = os.path.dirname(filename)
  400. if not os.path.exists(fact_dir):
  401. os.makedirs(fact_dir)
  402. with open(filename, 'w') as fact_file:
  403. fact_file.write(module.jsonify(facts))
  404. except (IOError, OSError) as ex:
  405. raise OpenShiftFactsFileWriteError(
  406. "Could not create fact file: %s, error: %s" % (filename, ex)
  407. )
  408. def get_local_facts_from_file(filename):
  409. """ Retrieve local facts from fact file
  410. Args:
  411. filename (str): local facts file
  412. Returns:
  413. dict: the retrieved facts
  414. """
  415. local_facts = dict()
  416. try:
  417. # Handle conversion of INI style facts file to json style
  418. ini_facts = ConfigParser.SafeConfigParser()
  419. ini_facts.read(filename)
  420. for section in ini_facts.sections():
  421. local_facts[section] = dict()
  422. for key, value in ini_facts.items(section):
  423. local_facts[section][key] = value
  424. except (ConfigParser.MissingSectionHeaderError,
  425. ConfigParser.ParsingError):
  426. try:
  427. with open(filename, 'r') as facts_file:
  428. local_facts = json.load(facts_file)
  429. except (ValueError, IOError):
  430. pass
  431. return local_facts
  432. class OpenShiftFactsUnsupportedRoleError(Exception):
  433. """OpenShift Facts Unsupported Role Error"""
  434. pass
  435. class OpenShiftFactsFileWriteError(Exception):
  436. """OpenShift Facts File Write Error"""
  437. pass
  438. class OpenShiftFactsMetadataUnavailableError(Exception):
  439. """OpenShift Facts Metadata Unavailable Error"""
  440. pass
  441. class OpenShiftFacts(object):
  442. """ OpenShift Facts
  443. Attributes:
  444. facts (dict): OpenShift facts for the host
  445. Args:
  446. role (str): role for setting local facts
  447. filename (str): local facts file to use
  448. local_facts (dict): local facts to set
  449. Raises:
  450. OpenShiftFactsUnsupportedRoleError:
  451. """
  452. known_roles = ['common', 'master', 'node', 'master_sdn', 'node_sdn', 'dns']
  453. def __init__(self, role, filename, local_facts):
  454. self.changed = False
  455. self.filename = filename
  456. if role not in self.known_roles:
  457. raise OpenShiftFactsUnsupportedRoleError(
  458. "Role %s is not supported by this module" % role
  459. )
  460. self.role = role
  461. self.system_facts = ansible_facts(module)
  462. self.facts = self.generate_facts(local_facts)
  463. def generate_facts(self, local_facts):
  464. """ Generate facts
  465. Args:
  466. local_facts (dict): local_facts for overriding generated
  467. defaults
  468. Returns:
  469. dict: The generated facts
  470. """
  471. local_facts = self.init_local_facts(local_facts)
  472. roles = local_facts.keys()
  473. defaults = self.get_defaults(roles)
  474. provider_facts = self.init_provider_facts()
  475. facts = apply_provider_facts(defaults, provider_facts, roles)
  476. facts = merge_facts(facts, local_facts)
  477. facts['current_config'] = get_current_config(facts)
  478. facts = set_url_facts_if_unset(facts)
  479. facts = set_fluentd_facts_if_unset(facts)
  480. return dict(openshift=facts)
  481. def get_defaults(self, roles):
  482. """ Get default fact values
  483. Args:
  484. roles (list): list of roles for this host
  485. Returns:
  486. dict: The generated default facts
  487. """
  488. defaults = dict()
  489. common = dict(use_openshift_sdn=True)
  490. ip_addr = self.system_facts['default_ipv4']['address']
  491. common['ip'] = ip_addr
  492. common['public_ip'] = ip_addr
  493. exit_code, output, _ = module.run_command(['hostname', '-f'])
  494. hostname_f = output.strip() if exit_code == 0 else ''
  495. hostname_values = [hostname_f, self.system_facts['nodename'],
  496. self.system_facts['fqdn']]
  497. hostname = choose_hostname(hostname_values)
  498. common['hostname'] = hostname
  499. common['public_hostname'] = hostname
  500. defaults['common'] = common
  501. if 'master' in roles:
  502. master = dict(api_use_ssl=True, api_port='8443',
  503. console_use_ssl=True, console_path='/console',
  504. console_port='8443', etcd_use_ssl=False,
  505. etcd_port='4001', portal_net='172.30.17.0/24')
  506. defaults['master'] = master
  507. if 'node' in roles:
  508. node = dict(external_id=common['hostname'], pod_cidr='',
  509. labels={}, annotations={})
  510. node['resources_cpu'] = self.system_facts['processor_cores']
  511. node['resources_memory'] = int(
  512. int(self.system_facts['memtotal_mb']) * 1024 * 1024 * 0.75
  513. )
  514. defaults['node'] = node
  515. return defaults
  516. def guess_host_provider(self):
  517. """ Guess the host provider
  518. Returns:
  519. dict: The generated default facts for the detected provider
  520. """
  521. # TODO: cloud provider facts should probably be submitted upstream
  522. product_name = self.system_facts['product_name']
  523. product_version = self.system_facts['product_version']
  524. virt_type = self.system_facts['virtualization_type']
  525. virt_role = self.system_facts['virtualization_role']
  526. provider = None
  527. metadata = None
  528. # TODO: this is not exposed through module_utils/facts.py in ansible,
  529. # need to create PR for ansible to expose it
  530. bios_vendor = get_file_content(
  531. '/sys/devices/virtual/dmi/id/bios_vendor'
  532. )
  533. if bios_vendor == 'Google':
  534. provider = 'gce'
  535. metadata_url = ('http://metadata.google.internal/'
  536. 'computeMetadata/v1/?recursive=true')
  537. headers = {'Metadata-Flavor': 'Google'}
  538. metadata = get_provider_metadata(metadata_url, True, headers,
  539. True)
  540. # Filter sshKeys and serviceAccounts from gce metadata
  541. if metadata:
  542. metadata['project']['attributes'].pop('sshKeys', None)
  543. metadata['instance'].pop('serviceAccounts', None)
  544. elif (virt_type == 'xen' and virt_role == 'guest'
  545. and re.match(r'.*\.amazon$', product_version)):
  546. provider = 'ec2'
  547. metadata_url = 'http://169.254.169.254/latest/meta-data/'
  548. metadata = get_provider_metadata(metadata_url)
  549. elif re.search(r'OpenStack', product_name):
  550. provider = 'openstack'
  551. metadata_url = ('http://169.254.169.254/openstack/latest/'
  552. 'meta_data.json')
  553. metadata = get_provider_metadata(metadata_url, True, None,
  554. True)
  555. if metadata:
  556. ec2_compat_url = 'http://169.254.169.254/latest/meta-data/'
  557. metadata['ec2_compat'] = get_provider_metadata(
  558. ec2_compat_url
  559. )
  560. # disable pylint maybe-no-member because overloaded use of
  561. # the module name causes pylint to not detect that results
  562. # is an array or hash
  563. # pylint: disable=maybe-no-member
  564. # Filter public_keys and random_seed from openstack metadata
  565. metadata.pop('public_keys', None)
  566. metadata.pop('random_seed', None)
  567. if not metadata['ec2_compat']:
  568. metadata = None
  569. return dict(name=provider, metadata=metadata)
  570. def init_provider_facts(self):
  571. """ Initialize the provider facts
  572. Returns:
  573. dict: The normalized provider facts
  574. """
  575. provider_info = self.guess_host_provider()
  576. provider_facts = normalize_provider_facts(
  577. provider_info.get('name'),
  578. provider_info.get('metadata')
  579. )
  580. return provider_facts
  581. def init_local_facts(self, facts=None):
  582. """ Initialize the provider facts
  583. Args:
  584. facts (dict): local facts to set
  585. Returns:
  586. dict: The result of merging the provided facts with existing
  587. local facts
  588. """
  589. changed = False
  590. facts_to_set = {self.role: dict()}
  591. if facts is not None:
  592. facts_to_set[self.role] = facts
  593. local_facts = get_local_facts_from_file(self.filename)
  594. for arg in ['labels', 'annotations']:
  595. if arg in facts_to_set and isinstance(facts_to_set[arg],
  596. basestring):
  597. facts_to_set[arg] = module.from_json(facts_to_set[arg])
  598. new_local_facts = merge_facts(local_facts, facts_to_set)
  599. for facts in new_local_facts.values():
  600. keys_to_delete = []
  601. for fact, value in facts.iteritems():
  602. if value == "" or value is None:
  603. keys_to_delete.append(fact)
  604. for key in keys_to_delete:
  605. del facts[key]
  606. if new_local_facts != local_facts:
  607. changed = True
  608. if not module.check_mode:
  609. save_local_facts(self.filename, new_local_facts)
  610. self.changed = changed
  611. return new_local_facts
  612. def main():
  613. """ main """
  614. # disabling pylint errors for global-variable-undefined and invalid-name
  615. # for 'global module' usage, since it is required to use ansible_facts
  616. # pylint: disable=global-variable-undefined, invalid-name
  617. global module
  618. module = AnsibleModule(
  619. argument_spec=dict(
  620. role=dict(default='common', required=False,
  621. choices=OpenShiftFacts.known_roles),
  622. local_facts=dict(default=None, type='dict', required=False),
  623. ),
  624. supports_check_mode=True,
  625. add_file_common_args=True,
  626. )
  627. role = module.params['role']
  628. local_facts = module.params['local_facts']
  629. fact_file = '/etc/ansible/facts.d/openshift.fact'
  630. openshift_facts = OpenShiftFacts(role, fact_file, local_facts)
  631. file_params = module.params.copy()
  632. file_params['path'] = fact_file
  633. file_args = module.load_file_common_arguments(file_params)
  634. changed = module.set_fs_attributes_if_different(file_args,
  635. openshift_facts.changed)
  636. return module.exit_json(changed=changed,
  637. ansible_facts=openshift_facts.facts)
  638. # ignore pylint errors related to the module_utils import
  639. # pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import
  640. # import module snippets
  641. from ansible.module_utils.basic import *
  642. from ansible.module_utils.facts import *
  643. from ansible.module_utils.urls import *
  644. if __name__ == '__main__':
  645. main()