You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

791 lines
26 KiB

  1. #!/usr/bin/env python2
  2. #
  3. # Copyright 2015 Cisco Systems, Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. # original: https://github.com/CiscoCloud/terraform.py
  18. """\
  19. Dynamic inventory for Terraform - finds all `.tfstate` files below the working
  20. directory and generates an inventory based on them.
  21. """
  22. from __future__ import unicode_literals, print_function
  23. import argparse
  24. from collections import defaultdict
  25. from functools import wraps
  26. import json
  27. import os
  28. import re
  29. VERSION = '0.3.0pre'
  30. def tfstates(root=None):
  31. root = root or os.getcwd()
  32. for dirpath, _, filenames in os.walk(root):
  33. for name in filenames:
  34. if os.path.splitext(name)[-1] == '.tfstate':
  35. yield os.path.join(dirpath, name)
  36. def iterresources(filenames):
  37. for filename in filenames:
  38. with open(filename, 'r') as json_file:
  39. state = json.load(json_file)
  40. for module in state['modules']:
  41. name = module['path'][-1]
  42. for key, resource in module['resources'].items():
  43. yield name, key, resource
  44. ## READ RESOURCES
  45. PARSERS = {}
  46. def _clean_dc(dcname):
  47. # Consul DCs are strictly alphanumeric with underscores and hyphens -
  48. # ensure that the consul_dc attribute meets these requirements.
  49. return re.sub('[^\w_\-]', '-', dcname)
  50. def iterhosts(resources):
  51. '''yield host tuples of (name, attributes, groups)'''
  52. for module_name, key, resource in resources:
  53. resource_type, name = key.split('.', 1)
  54. try:
  55. parser = PARSERS[resource_type]
  56. except KeyError:
  57. continue
  58. yield parser(resource, module_name)
  59. def iterips(resources):
  60. '''yield ip tuples of (instance_id, ip)'''
  61. for module_name, key, resource in resources:
  62. resource_type, name = key.split('.', 1)
  63. if resource_type == 'openstack_compute_floatingip_associate_v2':
  64. yield openstack_floating_ips(resource)
  65. def parses(prefix):
  66. def inner(func):
  67. PARSERS[prefix] = func
  68. return func
  69. return inner
  70. def calculate_mantl_vars(func):
  71. """calculate Mantl vars"""
  72. @wraps(func)
  73. def inner(*args, **kwargs):
  74. name, attrs, groups = func(*args, **kwargs)
  75. # attrs
  76. if attrs.get('role', '') == 'control':
  77. attrs['consul_is_server'] = True
  78. else:
  79. attrs['consul_is_server'] = False
  80. # groups
  81. if attrs.get('publicly_routable', False):
  82. groups.append('publicly_routable')
  83. return name, attrs, groups
  84. return inner
  85. def _parse_prefix(source, prefix, sep='.'):
  86. for compkey, value in source.items():
  87. try:
  88. curprefix, rest = compkey.split(sep, 1)
  89. except ValueError:
  90. continue
  91. if curprefix != prefix or rest == '#':
  92. continue
  93. yield rest, value
  94. def parse_attr_list(source, prefix, sep='.'):
  95. attrs = defaultdict(dict)
  96. for compkey, value in _parse_prefix(source, prefix, sep):
  97. idx, key = compkey.split(sep, 1)
  98. attrs[idx][key] = value
  99. return attrs.values()
  100. def parse_dict(source, prefix, sep='.'):
  101. return dict(_parse_prefix(source, prefix, sep))
  102. def parse_list(source, prefix, sep='.'):
  103. return [value for _, value in _parse_prefix(source, prefix, sep)]
  104. def parse_bool(string_form):
  105. token = string_form.lower()[0]
  106. if token == 't':
  107. return True
  108. elif token == 'f':
  109. return False
  110. else:
  111. raise ValueError('could not convert %r to a bool' % string_form)
  112. @parses('triton_machine')
  113. @calculate_mantl_vars
  114. def triton_machine(resource, module_name):
  115. raw_attrs = resource['primary']['attributes']
  116. name = raw_attrs.get('name')
  117. groups = []
  118. attrs = {
  119. 'id': raw_attrs['id'],
  120. 'dataset': raw_attrs['dataset'],
  121. 'disk': raw_attrs['disk'],
  122. 'firewall_enabled': parse_bool(raw_attrs['firewall_enabled']),
  123. 'image': raw_attrs['image'],
  124. 'ips': parse_list(raw_attrs, 'ips'),
  125. 'memory': raw_attrs['memory'],
  126. 'name': raw_attrs['name'],
  127. 'networks': parse_list(raw_attrs, 'networks'),
  128. 'package': raw_attrs['package'],
  129. 'primary_ip': raw_attrs['primaryip'],
  130. 'root_authorized_keys': raw_attrs['root_authorized_keys'],
  131. 'state': raw_attrs['state'],
  132. 'tags': parse_dict(raw_attrs, 'tags'),
  133. 'type': raw_attrs['type'],
  134. 'user_data': raw_attrs['user_data'],
  135. 'user_script': raw_attrs['user_script'],
  136. # ansible
  137. 'ansible_ssh_host': raw_attrs['primaryip'],
  138. 'ansible_ssh_port': 22,
  139. 'ansible_ssh_user': 'root', # it's "root" on Triton by default
  140. # generic
  141. 'public_ipv4': raw_attrs['primaryip'],
  142. 'provider': 'triton',
  143. }
  144. # private IPv4
  145. for ip in attrs['ips']:
  146. if ip.startswith('10') or ip.startswith('192.168'): # private IPs
  147. attrs['private_ipv4'] = ip
  148. break
  149. if 'private_ipv4' not in attrs:
  150. attrs['private_ipv4'] = attrs['public_ipv4']
  151. # attrs specific to Mantl
  152. attrs.update({
  153. 'consul_dc': _clean_dc(attrs['tags'].get('dc', 'none')),
  154. 'role': attrs['tags'].get('role', 'none'),
  155. 'ansible_python_interpreter': attrs['tags'].get('python_bin', 'python')
  156. })
  157. # add groups based on attrs
  158. groups.append('triton_image=' + attrs['image'])
  159. groups.append('triton_package=' + attrs['package'])
  160. groups.append('triton_state=' + attrs['state'])
  161. groups.append('triton_firewall_enabled=%s' % attrs['firewall_enabled'])
  162. groups.extend('triton_tags_%s=%s' % item
  163. for item in attrs['tags'].items())
  164. groups.extend('triton_network=' + network
  165. for network in attrs['networks'])
  166. # groups specific to Mantl
  167. groups.append('role=' + attrs['role'])
  168. groups.append('dc=' + attrs['consul_dc'])
  169. return name, attrs, groups
  170. @parses('digitalocean_droplet')
  171. @calculate_mantl_vars
  172. def digitalocean_host(resource, tfvars=None):
  173. raw_attrs = resource['primary']['attributes']
  174. name = raw_attrs['name']
  175. groups = []
  176. attrs = {
  177. 'id': raw_attrs['id'],
  178. 'image': raw_attrs['image'],
  179. 'ipv4_address': raw_attrs['ipv4_address'],
  180. 'locked': parse_bool(raw_attrs['locked']),
  181. 'metadata': json.loads(raw_attrs.get('user_data', '{}')),
  182. 'region': raw_attrs['region'],
  183. 'size': raw_attrs['size'],
  184. 'ssh_keys': parse_list(raw_attrs, 'ssh_keys'),
  185. 'status': raw_attrs['status'],
  186. # ansible
  187. 'ansible_ssh_host': raw_attrs['ipv4_address'],
  188. 'ansible_ssh_port': 22,
  189. 'ansible_ssh_user': 'root', # it's always "root" on DO
  190. # generic
  191. 'public_ipv4': raw_attrs['ipv4_address'],
  192. 'private_ipv4': raw_attrs.get('ipv4_address_private',
  193. raw_attrs['ipv4_address']),
  194. 'provider': 'digitalocean',
  195. }
  196. # attrs specific to Mantl
  197. attrs.update({
  198. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', attrs['region'])),
  199. 'role': attrs['metadata'].get('role', 'none'),
  200. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  201. })
  202. # add groups based on attrs
  203. groups.append('do_image=' + attrs['image'])
  204. groups.append('do_locked=%s' % attrs['locked'])
  205. groups.append('do_region=' + attrs['region'])
  206. groups.append('do_size=' + attrs['size'])
  207. groups.append('do_status=' + attrs['status'])
  208. groups.extend('do_metadata_%s=%s' % item
  209. for item in attrs['metadata'].items())
  210. # groups specific to Mantl
  211. groups.append('role=' + attrs['role'])
  212. groups.append('dc=' + attrs['consul_dc'])
  213. return name, attrs, groups
  214. @parses('softlayer_virtualserver')
  215. @calculate_mantl_vars
  216. def softlayer_host(resource, module_name):
  217. raw_attrs = resource['primary']['attributes']
  218. name = raw_attrs['name']
  219. groups = []
  220. attrs = {
  221. 'id': raw_attrs['id'],
  222. 'image': raw_attrs['image'],
  223. 'ipv4_address': raw_attrs['ipv4_address'],
  224. 'metadata': json.loads(raw_attrs.get('user_data', '{}')),
  225. 'region': raw_attrs['region'],
  226. 'ram': raw_attrs['ram'],
  227. 'cpu': raw_attrs['cpu'],
  228. 'ssh_keys': parse_list(raw_attrs, 'ssh_keys'),
  229. 'public_ipv4': raw_attrs['ipv4_address'],
  230. 'private_ipv4': raw_attrs['ipv4_address_private'],
  231. 'ansible_ssh_host': raw_attrs['ipv4_address'],
  232. 'ansible_ssh_port': 22,
  233. 'ansible_ssh_user': 'root',
  234. 'provider': 'softlayer',
  235. }
  236. # attrs specific to Mantl
  237. attrs.update({
  238. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', attrs['region'])),
  239. 'role': attrs['metadata'].get('role', 'none'),
  240. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  241. })
  242. # groups specific to Mantl
  243. groups.append('role=' + attrs['role'])
  244. groups.append('dc=' + attrs['consul_dc'])
  245. return name, attrs, groups
  246. def openstack_floating_ips(resource):
  247. raw_attrs = resource['primary']['attributes']
  248. attrs = {
  249. 'ip': raw_attrs['floating_ip'],
  250. 'instance_id': raw_attrs['instance_id'],
  251. }
  252. return attrs
  253. def openstack_floating_ips(resource):
  254. raw_attrs = resource['primary']['attributes']
  255. return raw_attrs['instance_id'], raw_attrs['floating_ip']
  256. @parses('openstack_compute_instance_v2')
  257. @calculate_mantl_vars
  258. def openstack_host(resource, module_name):
  259. raw_attrs = resource['primary']['attributes']
  260. name = raw_attrs['name']
  261. groups = []
  262. attrs = {
  263. 'access_ip_v4': raw_attrs['access_ip_v4'],
  264. 'access_ip_v6': raw_attrs['access_ip_v6'],
  265. 'ip': raw_attrs['network.0.fixed_ip_v4'],
  266. 'flavor': parse_dict(raw_attrs, 'flavor',
  267. sep='_'),
  268. 'id': raw_attrs['id'],
  269. 'image': parse_dict(raw_attrs, 'image',
  270. sep='_'),
  271. 'key_pair': raw_attrs['key_pair'],
  272. 'metadata': parse_dict(raw_attrs, 'metadata'),
  273. 'network': parse_attr_list(raw_attrs, 'network'),
  274. 'region': raw_attrs.get('region', ''),
  275. 'security_groups': parse_list(raw_attrs, 'security_groups'),
  276. # ansible
  277. 'ansible_ssh_port': 22,
  278. # workaround for an OpenStack bug where hosts have a different domain
  279. # after they're restarted
  280. 'host_domain': 'novalocal',
  281. 'use_host_domain': True,
  282. # generic
  283. 'public_ipv4': raw_attrs['access_ip_v4'],
  284. 'private_ipv4': raw_attrs['access_ip_v4'],
  285. 'provider': 'openstack',
  286. }
  287. if 'floating_ip' in raw_attrs:
  288. attrs['private_ipv4'] = raw_attrs['network.0.fixed_ip_v4']
  289. try:
  290. attrs.update({
  291. 'ansible_ssh_host': raw_attrs['access_ip_v4'],
  292. 'publicly_routable': True,
  293. })
  294. except (KeyError, ValueError):
  295. attrs.update({'ansible_ssh_host': '', 'publicly_routable': False})
  296. # Handling of floating IPs has changed: https://github.com/terraform-providers/terraform-provider-openstack/blob/master/CHANGELOG.md#010-june-21-2017
  297. # attrs specific to Ansible
  298. if 'metadata.ssh_user' in raw_attrs:
  299. attrs['ansible_ssh_user'] = raw_attrs['metadata.ssh_user']
  300. if 'volume.#' in raw_attrs.keys() and int(raw_attrs['volume.#']) > 0:
  301. device_index = 1
  302. for key, value in raw_attrs.items():
  303. match = re.search("^volume.*.device$", key)
  304. if match:
  305. attrs['disk_volume_device_'+str(device_index)] = value
  306. device_index += 1
  307. # attrs specific to Mantl
  308. attrs.update({
  309. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  310. 'role': attrs['metadata'].get('role', 'none'),
  311. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  312. })
  313. # add groups based on attrs
  314. groups.append('os_image=' + attrs['image']['name'])
  315. groups.append('os_flavor=' + attrs['flavor']['name'])
  316. groups.extend('os_metadata_%s=%s' % item
  317. for item in attrs['metadata'].items())
  318. groups.append('os_region=' + attrs['region'])
  319. # groups specific to Mantl
  320. groups.append('role=' + attrs['metadata'].get('role', 'none'))
  321. groups.append('dc=' + attrs['consul_dc'])
  322. # groups specific to kubespray
  323. for group in attrs['metadata'].get('kubespray_groups', "").split(","):
  324. groups.append(group)
  325. return name, attrs, groups
  326. @parses('aws_instance')
  327. @calculate_mantl_vars
  328. def aws_host(resource, module_name):
  329. name = resource['primary']['attributes']['tags.Name']
  330. raw_attrs = resource['primary']['attributes']
  331. groups = []
  332. attrs = {
  333. 'ami': raw_attrs['ami'],
  334. 'availability_zone': raw_attrs['availability_zone'],
  335. 'ebs_block_device': parse_attr_list(raw_attrs, 'ebs_block_device'),
  336. 'ebs_optimized': parse_bool(raw_attrs['ebs_optimized']),
  337. 'ephemeral_block_device': parse_attr_list(raw_attrs,
  338. 'ephemeral_block_device'),
  339. 'id': raw_attrs['id'],
  340. 'key_name': raw_attrs['key_name'],
  341. 'private': parse_dict(raw_attrs, 'private',
  342. sep='_'),
  343. 'public': parse_dict(raw_attrs, 'public',
  344. sep='_'),
  345. 'root_block_device': parse_attr_list(raw_attrs, 'root_block_device'),
  346. 'security_groups': parse_list(raw_attrs, 'security_groups'),
  347. 'subnet': parse_dict(raw_attrs, 'subnet',
  348. sep='_'),
  349. 'tags': parse_dict(raw_attrs, 'tags'),
  350. 'tenancy': raw_attrs['tenancy'],
  351. 'vpc_security_group_ids': parse_list(raw_attrs,
  352. 'vpc_security_group_ids'),
  353. # ansible-specific
  354. 'ansible_ssh_port': 22,
  355. 'ansible_ssh_host': raw_attrs['public_ip'],
  356. # generic
  357. 'public_ipv4': raw_attrs['public_ip'],
  358. 'private_ipv4': raw_attrs['private_ip'],
  359. 'provider': 'aws',
  360. }
  361. # attrs specific to Ansible
  362. if 'tags.sshUser' in raw_attrs:
  363. attrs['ansible_ssh_user'] = raw_attrs['tags.sshUser']
  364. if 'tags.sshPrivateIp' in raw_attrs:
  365. attrs['ansible_ssh_host'] = raw_attrs['private_ip']
  366. # attrs specific to Mantl
  367. attrs.update({
  368. 'consul_dc': _clean_dc(attrs['tags'].get('dc', module_name)),
  369. 'role': attrs['tags'].get('role', 'none'),
  370. 'ansible_python_interpreter': attrs['tags'].get('python_bin','python')
  371. })
  372. # groups specific to Mantl
  373. groups.extend(['aws_ami=' + attrs['ami'],
  374. 'aws_az=' + attrs['availability_zone'],
  375. 'aws_key_name=' + attrs['key_name'],
  376. 'aws_tenancy=' + attrs['tenancy']])
  377. groups.extend('aws_tag_%s=%s' % item for item in attrs['tags'].items())
  378. groups.extend('aws_vpc_security_group=' + group
  379. for group in attrs['vpc_security_group_ids'])
  380. groups.extend('aws_subnet_%s=%s' % subnet
  381. for subnet in attrs['subnet'].items())
  382. # groups specific to Mantl
  383. groups.append('role=' + attrs['role'])
  384. groups.append('dc=' + attrs['consul_dc'])
  385. return name, attrs, groups
  386. @parses('google_compute_instance')
  387. @calculate_mantl_vars
  388. def gce_host(resource, module_name):
  389. name = resource['primary']['id']
  390. raw_attrs = resource['primary']['attributes']
  391. groups = []
  392. # network interfaces
  393. interfaces = parse_attr_list(raw_attrs, 'network_interface')
  394. for interface in interfaces:
  395. interface['access_config'] = parse_attr_list(interface,
  396. 'access_config')
  397. for key in interface.keys():
  398. if '.' in key:
  399. del interface[key]
  400. # general attrs
  401. attrs = {
  402. 'can_ip_forward': raw_attrs['can_ip_forward'] == 'true',
  403. 'disks': parse_attr_list(raw_attrs, 'disk'),
  404. 'machine_type': raw_attrs['machine_type'],
  405. 'metadata': parse_dict(raw_attrs, 'metadata'),
  406. 'network': parse_attr_list(raw_attrs, 'network'),
  407. 'network_interface': interfaces,
  408. 'self_link': raw_attrs['self_link'],
  409. 'service_account': parse_attr_list(raw_attrs, 'service_account'),
  410. 'tags': parse_list(raw_attrs, 'tags'),
  411. 'zone': raw_attrs['zone'],
  412. # ansible
  413. 'ansible_ssh_port': 22,
  414. 'provider': 'gce',
  415. }
  416. # attrs specific to Ansible
  417. if 'metadata.ssh_user' in raw_attrs:
  418. attrs['ansible_ssh_user'] = raw_attrs['metadata.ssh_user']
  419. # attrs specific to Mantl
  420. attrs.update({
  421. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  422. 'role': attrs['metadata'].get('role', 'none'),
  423. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  424. })
  425. try:
  426. attrs.update({
  427. 'ansible_ssh_host': interfaces[0]['access_config'][0]['nat_ip'] or interfaces[0]['access_config'][0]['assigned_nat_ip'],
  428. 'public_ipv4': interfaces[0]['access_config'][0]['nat_ip'] or interfaces[0]['access_config'][0]['assigned_nat_ip'],
  429. 'private_ipv4': interfaces[0]['address'],
  430. 'publicly_routable': True,
  431. })
  432. except (KeyError, ValueError):
  433. attrs.update({'ansible_ssh_host': '', 'publicly_routable': False})
  434. # add groups based on attrs
  435. groups.extend('gce_image=' + disk['image'] for disk in attrs['disks'])
  436. groups.append('gce_machine_type=' + attrs['machine_type'])
  437. groups.extend('gce_metadata_%s=%s' % (key, value)
  438. for (key, value) in attrs['metadata'].items()
  439. if key not in set(['sshKeys']))
  440. groups.extend('gce_tag=' + tag for tag in attrs['tags'])
  441. groups.append('gce_zone=' + attrs['zone'])
  442. if attrs['can_ip_forward']:
  443. groups.append('gce_ip_forward')
  444. if attrs['publicly_routable']:
  445. groups.append('gce_publicly_routable')
  446. # groups specific to Mantl
  447. groups.append('role=' + attrs['metadata'].get('role', 'none'))
  448. groups.append('dc=' + attrs['consul_dc'])
  449. return name, attrs, groups
  450. @parses('vsphere_virtual_machine')
  451. @calculate_mantl_vars
  452. def vsphere_host(resource, module_name):
  453. raw_attrs = resource['primary']['attributes']
  454. network_attrs = parse_dict(raw_attrs, 'network_interface')
  455. network = parse_dict(network_attrs, '0')
  456. ip_address = network.get('ipv4_address', network['ip_address'])
  457. name = raw_attrs['name']
  458. groups = []
  459. attrs = {
  460. 'id': raw_attrs['id'],
  461. 'ip_address': ip_address,
  462. 'private_ipv4': ip_address,
  463. 'public_ipv4': ip_address,
  464. 'metadata': parse_dict(raw_attrs, 'custom_configuration_parameters'),
  465. 'ansible_ssh_port': 22,
  466. 'provider': 'vsphere',
  467. }
  468. try:
  469. attrs.update({
  470. 'ansible_ssh_host': ip_address,
  471. })
  472. except (KeyError, ValueError):
  473. attrs.update({'ansible_ssh_host': '', })
  474. attrs.update({
  475. 'consul_dc': _clean_dc(attrs['metadata'].get('consul_dc', module_name)),
  476. 'role': attrs['metadata'].get('role', 'none'),
  477. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  478. })
  479. # attrs specific to Ansible
  480. if 'ssh_user' in attrs['metadata']:
  481. attrs['ansible_ssh_user'] = attrs['metadata']['ssh_user']
  482. groups.append('role=' + attrs['role'])
  483. groups.append('dc=' + attrs['consul_dc'])
  484. return name, attrs, groups
  485. @parses('azure_instance')
  486. @calculate_mantl_vars
  487. def azure_host(resource, module_name):
  488. name = resource['primary']['attributes']['name']
  489. raw_attrs = resource['primary']['attributes']
  490. groups = []
  491. attrs = {
  492. 'automatic_updates': raw_attrs['automatic_updates'],
  493. 'description': raw_attrs['description'],
  494. 'hosted_service_name': raw_attrs['hosted_service_name'],
  495. 'id': raw_attrs['id'],
  496. 'image': raw_attrs['image'],
  497. 'ip_address': raw_attrs['ip_address'],
  498. 'location': raw_attrs['location'],
  499. 'name': raw_attrs['name'],
  500. 'reverse_dns': raw_attrs['reverse_dns'],
  501. 'security_group': raw_attrs['security_group'],
  502. 'size': raw_attrs['size'],
  503. 'ssh_key_thumbprint': raw_attrs['ssh_key_thumbprint'],
  504. 'subnet': raw_attrs['subnet'],
  505. 'username': raw_attrs['username'],
  506. 'vip_address': raw_attrs['vip_address'],
  507. 'virtual_network': raw_attrs['virtual_network'],
  508. 'endpoint': parse_attr_list(raw_attrs, 'endpoint'),
  509. # ansible
  510. 'ansible_ssh_port': 22,
  511. 'ansible_ssh_user': raw_attrs['username'],
  512. 'ansible_ssh_host': raw_attrs['vip_address'],
  513. }
  514. # attrs specific to mantl
  515. attrs.update({
  516. 'consul_dc': attrs['location'].lower().replace(" ", "-"),
  517. 'role': attrs['description']
  518. })
  519. # groups specific to mantl
  520. groups.extend(['azure_image=' + attrs['image'],
  521. 'azure_location=' + attrs['location'].lower().replace(" ", "-"),
  522. 'azure_username=' + attrs['username'],
  523. 'azure_security_group=' + attrs['security_group']])
  524. # groups specific to mantl
  525. groups.append('role=' + attrs['role'])
  526. groups.append('dc=' + attrs['consul_dc'])
  527. return name, attrs, groups
  528. @parses('clc_server')
  529. @calculate_mantl_vars
  530. def clc_server(resource, module_name):
  531. raw_attrs = resource['primary']['attributes']
  532. name = raw_attrs.get('id')
  533. groups = []
  534. md = parse_dict(raw_attrs, 'metadata')
  535. attrs = {
  536. 'metadata': md,
  537. 'ansible_ssh_port': md.get('ssh_port', 22),
  538. 'ansible_ssh_user': md.get('ssh_user', 'root'),
  539. 'provider': 'clc',
  540. 'publicly_routable': False,
  541. }
  542. try:
  543. attrs.update({
  544. 'public_ipv4': raw_attrs['public_ip_address'],
  545. 'private_ipv4': raw_attrs['private_ip_address'],
  546. 'ansible_ssh_host': raw_attrs['public_ip_address'],
  547. 'publicly_routable': True,
  548. })
  549. except (KeyError, ValueError):
  550. attrs.update({
  551. 'ansible_ssh_host': raw_attrs['private_ip_address'],
  552. 'private_ipv4': raw_attrs['private_ip_address'],
  553. })
  554. attrs.update({
  555. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  556. 'role': attrs['metadata'].get('role', 'none'),
  557. })
  558. groups.append('role=' + attrs['role'])
  559. groups.append('dc=' + attrs['consul_dc'])
  560. return name, attrs, groups
  561. def iter_host_ips(hosts, ips):
  562. '''Update hosts that have an entry in the floating IP list'''
  563. for host in hosts:
  564. host_id = host[1]['id']
  565. if host_id in ips:
  566. ip = ips[host_id]
  567. host[1].update({
  568. 'access_ip_v4': ip,
  569. 'public_ipv4': ip,
  570. 'ansible_ssh_host': ip,
  571. })
  572. yield host
  573. ## QUERY TYPES
  574. def query_host(hosts, target):
  575. for name, attrs, _ in hosts:
  576. if name == target:
  577. return attrs
  578. return {}
  579. def query_list(hosts):
  580. groups = defaultdict(dict)
  581. meta = {}
  582. for name, attrs, hostgroups in hosts:
  583. for group in set(hostgroups):
  584. # Ansible 2.6.2 stopped supporting empty group names: https://github.com/ansible/ansible/pull/42584/commits/d4cd474b42ed23d8f8aabb2a7f84699673852eaf
  585. # Empty group name defaults to "all" in Ansible < 2.6.2 so we alter empty group names to "all"
  586. if not group: group = "all"
  587. groups[group].setdefault('hosts', [])
  588. groups[group]['hosts'].append(name)
  589. meta[name] = attrs
  590. groups['_meta'] = {'hostvars': meta}
  591. return groups
  592. def query_hostfile(hosts):
  593. out = ['## begin hosts generated by terraform.py ##']
  594. out.extend(
  595. '{}\t{}'.format(attrs['ansible_ssh_host'].ljust(16), name)
  596. for name, attrs, _ in hosts
  597. )
  598. out.append('## end hosts generated by terraform.py ##')
  599. return '\n'.join(out)
  600. def main():
  601. parser = argparse.ArgumentParser(
  602. __file__, __doc__,
  603. formatter_class=argparse.ArgumentDefaultsHelpFormatter, )
  604. modes = parser.add_mutually_exclusive_group(required=True)
  605. modes.add_argument('--list',
  606. action='store_true',
  607. help='list all variables')
  608. modes.add_argument('--host', help='list variables for a single host')
  609. modes.add_argument('--version',
  610. action='store_true',
  611. help='print version and exit')
  612. modes.add_argument('--hostfile',
  613. action='store_true',
  614. help='print hosts as a /etc/hosts snippet')
  615. parser.add_argument('--pretty',
  616. action='store_true',
  617. help='pretty-print output JSON')
  618. parser.add_argument('--nometa',
  619. action='store_true',
  620. help='with --list, exclude hostvars')
  621. default_root = os.environ.get('TERRAFORM_STATE_ROOT',
  622. os.path.abspath(os.path.join(os.path.dirname(__file__),
  623. '..', '..', )))
  624. parser.add_argument('--root',
  625. default=default_root,
  626. help='custom root to search for `.tfstate`s in')
  627. args = parser.parse_args()
  628. if args.version:
  629. print('%s %s' % (__file__, VERSION))
  630. parser.exit()
  631. hosts = iterhosts(iterresources(tfstates(args.root)))
  632. # Perform a second pass on the file to pick up floating_ip entries to update the ip address of referenced hosts
  633. ips = dict(iterips(iterresources(tfstates(args.root))))
  634. if ips:
  635. hosts = iter_host_ips(hosts, ips)
  636. if args.list:
  637. output = query_list(hosts)
  638. if args.nometa:
  639. del output['_meta']
  640. print(json.dumps(output, indent=4 if args.pretty else None))
  641. elif args.host:
  642. output = query_host(hosts, args.host)
  643. print(json.dumps(output, indent=4 if args.pretty else None))
  644. elif args.hostfile:
  645. output = query_hostfile(hosts)
  646. print(output)
  647. parser.exit()
  648. if __name__ == '__main__':
  649. main()