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.

746 lines
24 KiB

  1. #!/usr/bin/env python
  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 parses(prefix):
  60. def inner(func):
  61. PARSERS[prefix] = func
  62. return func
  63. return inner
  64. def calculate_mantl_vars(func):
  65. """calculate Mantl vars"""
  66. @wraps(func)
  67. def inner(*args, **kwargs):
  68. name, attrs, groups = func(*args, **kwargs)
  69. # attrs
  70. if attrs.get('role', '') == 'control':
  71. attrs['consul_is_server'] = True
  72. else:
  73. attrs['consul_is_server'] = False
  74. # groups
  75. if attrs.get('publicly_routable', False):
  76. groups.append('publicly_routable')
  77. return name, attrs, groups
  78. return inner
  79. def _parse_prefix(source, prefix, sep='.'):
  80. for compkey, value in source.items():
  81. try:
  82. curprefix, rest = compkey.split(sep, 1)
  83. except ValueError:
  84. continue
  85. if curprefix != prefix or rest == '#':
  86. continue
  87. yield rest, value
  88. def parse_attr_list(source, prefix, sep='.'):
  89. attrs = defaultdict(dict)
  90. for compkey, value in _parse_prefix(source, prefix, sep):
  91. idx, key = compkey.split(sep, 1)
  92. attrs[idx][key] = value
  93. return attrs.values()
  94. def parse_dict(source, prefix, sep='.'):
  95. return dict(_parse_prefix(source, prefix, sep))
  96. def parse_list(source, prefix, sep='.'):
  97. return [value for _, value in _parse_prefix(source, prefix, sep)]
  98. def parse_bool(string_form):
  99. token = string_form.lower()[0]
  100. if token == 't':
  101. return True
  102. elif token == 'f':
  103. return False
  104. else:
  105. raise ValueError('could not convert %r to a bool' % string_form)
  106. @parses('triton_machine')
  107. @calculate_mantl_vars
  108. def triton_machine(resource, module_name):
  109. raw_attrs = resource['primary']['attributes']
  110. name = raw_attrs.get('name')
  111. groups = []
  112. attrs = {
  113. 'id': raw_attrs['id'],
  114. 'dataset': raw_attrs['dataset'],
  115. 'disk': raw_attrs['disk'],
  116. 'firewall_enabled': parse_bool(raw_attrs['firewall_enabled']),
  117. 'image': raw_attrs['image'],
  118. 'ips': parse_list(raw_attrs, 'ips'),
  119. 'memory': raw_attrs['memory'],
  120. 'name': raw_attrs['name'],
  121. 'networks': parse_list(raw_attrs, 'networks'),
  122. 'package': raw_attrs['package'],
  123. 'primary_ip': raw_attrs['primaryip'],
  124. 'root_authorized_keys': raw_attrs['root_authorized_keys'],
  125. 'state': raw_attrs['state'],
  126. 'tags': parse_dict(raw_attrs, 'tags'),
  127. 'type': raw_attrs['type'],
  128. 'user_data': raw_attrs['user_data'],
  129. 'user_script': raw_attrs['user_script'],
  130. # ansible
  131. 'ansible_ssh_host': raw_attrs['primaryip'],
  132. 'ansible_ssh_port': 22,
  133. 'ansible_ssh_user': 'root', # it's "root" on Triton by default
  134. # generic
  135. 'public_ipv4': raw_attrs['primaryip'],
  136. 'provider': 'triton',
  137. }
  138. # private IPv4
  139. for ip in attrs['ips']:
  140. if ip.startswith('10') or ip.startswith('192.168'): # private IPs
  141. attrs['private_ipv4'] = ip
  142. break
  143. if 'private_ipv4' not in attrs:
  144. attrs['private_ipv4'] = attrs['public_ipv4']
  145. # attrs specific to Mantl
  146. attrs.update({
  147. 'consul_dc': _clean_dc(attrs['tags'].get('dc', 'none')),
  148. 'role': attrs['tags'].get('role', 'none'),
  149. 'ansible_python_interpreter': attrs['tags'].get('python_bin', 'python')
  150. })
  151. # add groups based on attrs
  152. groups.append('triton_image=' + attrs['image'])
  153. groups.append('triton_package=' + attrs['package'])
  154. groups.append('triton_state=' + attrs['state'])
  155. groups.append('triton_firewall_enabled=%s' % attrs['firewall_enabled'])
  156. groups.extend('triton_tags_%s=%s' % item
  157. for item in attrs['tags'].items())
  158. groups.extend('triton_network=' + network
  159. for network in attrs['networks'])
  160. # groups specific to Mantl
  161. groups.append('role=' + attrs['role'])
  162. groups.append('dc=' + attrs['consul_dc'])
  163. return name, attrs, groups
  164. @parses('digitalocean_droplet')
  165. @calculate_mantl_vars
  166. def digitalocean_host(resource, tfvars=None):
  167. raw_attrs = resource['primary']['attributes']
  168. name = raw_attrs['name']
  169. groups = []
  170. attrs = {
  171. 'id': raw_attrs['id'],
  172. 'image': raw_attrs['image'],
  173. 'ipv4_address': raw_attrs['ipv4_address'],
  174. 'locked': parse_bool(raw_attrs['locked']),
  175. 'metadata': json.loads(raw_attrs.get('user_data', '{}')),
  176. 'region': raw_attrs['region'],
  177. 'size': raw_attrs['size'],
  178. 'ssh_keys': parse_list(raw_attrs, 'ssh_keys'),
  179. 'status': raw_attrs['status'],
  180. # ansible
  181. 'ansible_ssh_host': raw_attrs['ipv4_address'],
  182. 'ansible_ssh_port': 22,
  183. 'ansible_ssh_user': 'root', # it's always "root" on DO
  184. # generic
  185. 'public_ipv4': raw_attrs['ipv4_address'],
  186. 'private_ipv4': raw_attrs.get('ipv4_address_private',
  187. raw_attrs['ipv4_address']),
  188. 'provider': 'digitalocean',
  189. }
  190. # attrs specific to Mantl
  191. attrs.update({
  192. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', attrs['region'])),
  193. 'role': attrs['metadata'].get('role', 'none'),
  194. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  195. })
  196. # add groups based on attrs
  197. groups.append('do_image=' + attrs['image'])
  198. groups.append('do_locked=%s' % attrs['locked'])
  199. groups.append('do_region=' + attrs['region'])
  200. groups.append('do_size=' + attrs['size'])
  201. groups.append('do_status=' + attrs['status'])
  202. groups.extend('do_metadata_%s=%s' % item
  203. for item in attrs['metadata'].items())
  204. # groups specific to Mantl
  205. groups.append('role=' + attrs['role'])
  206. groups.append('dc=' + attrs['consul_dc'])
  207. return name, attrs, groups
  208. @parses('softlayer_virtualserver')
  209. @calculate_mantl_vars
  210. def softlayer_host(resource, module_name):
  211. raw_attrs = resource['primary']['attributes']
  212. name = raw_attrs['name']
  213. groups = []
  214. attrs = {
  215. 'id': raw_attrs['id'],
  216. 'image': raw_attrs['image'],
  217. 'ipv4_address': raw_attrs['ipv4_address'],
  218. 'metadata': json.loads(raw_attrs.get('user_data', '{}')),
  219. 'region': raw_attrs['region'],
  220. 'ram': raw_attrs['ram'],
  221. 'cpu': raw_attrs['cpu'],
  222. 'ssh_keys': parse_list(raw_attrs, 'ssh_keys'),
  223. 'public_ipv4': raw_attrs['ipv4_address'],
  224. 'private_ipv4': raw_attrs['ipv4_address_private'],
  225. 'ansible_ssh_host': raw_attrs['ipv4_address'],
  226. 'ansible_ssh_port': 22,
  227. 'ansible_ssh_user': 'root',
  228. 'provider': 'softlayer',
  229. }
  230. # attrs specific to Mantl
  231. attrs.update({
  232. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', attrs['region'])),
  233. 'role': attrs['metadata'].get('role', 'none'),
  234. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  235. })
  236. # groups specific to Mantl
  237. groups.append('role=' + attrs['role'])
  238. groups.append('dc=' + attrs['consul_dc'])
  239. return name, attrs, groups
  240. @parses('openstack_compute_instance_v2')
  241. @calculate_mantl_vars
  242. def openstack_host(resource, module_name):
  243. raw_attrs = resource['primary']['attributes']
  244. name = raw_attrs['name']
  245. groups = []
  246. attrs = {
  247. 'access_ip_v4': raw_attrs['access_ip_v4'],
  248. 'access_ip_v6': raw_attrs['access_ip_v6'],
  249. 'ip': raw_attrs['network.0.fixed_ip_v4'],
  250. 'flavor': parse_dict(raw_attrs, 'flavor',
  251. sep='_'),
  252. 'id': raw_attrs['id'],
  253. 'image': parse_dict(raw_attrs, 'image',
  254. sep='_'),
  255. 'key_pair': raw_attrs['key_pair'],
  256. 'metadata': parse_dict(raw_attrs, 'metadata'),
  257. 'network': parse_attr_list(raw_attrs, 'network'),
  258. 'region': raw_attrs.get('region', ''),
  259. 'security_groups': parse_list(raw_attrs, 'security_groups'),
  260. # ansible
  261. 'ansible_ssh_port': 22,
  262. # workaround for an OpenStack bug where hosts have a different domain
  263. # after they're restarted
  264. 'host_domain': 'novalocal',
  265. 'use_host_domain': True,
  266. # generic
  267. 'public_ipv4': raw_attrs['access_ip_v4'],
  268. 'private_ipv4': raw_attrs['access_ip_v4'],
  269. 'provider': 'openstack',
  270. }
  271. if 'floating_ip' in raw_attrs:
  272. attrs['private_ipv4'] = raw_attrs['network.0.fixed_ip_v4']
  273. try:
  274. attrs.update({
  275. 'ansible_ssh_host': raw_attrs['access_ip_v4'],
  276. 'publicly_routable': True,
  277. })
  278. except (KeyError, ValueError):
  279. attrs.update({'ansible_ssh_host': '', 'publicly_routable': False})
  280. # attrs specific to Ansible
  281. if 'metadata.ssh_user' in raw_attrs:
  282. attrs['ansible_ssh_user'] = raw_attrs['metadata.ssh_user']
  283. if 'volume.#' in raw_attrs.keys() and int(raw_attrs['volume.#']) > 0:
  284. device_index = 1
  285. for key, value in raw_attrs.items():
  286. match = re.search("^volume.*.device$", key)
  287. if match:
  288. attrs['disk_volume_device_'+str(device_index)] = value
  289. device_index += 1
  290. # attrs specific to Mantl
  291. attrs.update({
  292. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  293. 'role': attrs['metadata'].get('role', 'none'),
  294. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  295. })
  296. # add groups based on attrs
  297. groups.append('os_image=' + attrs['image']['name'])
  298. groups.append('os_flavor=' + attrs['flavor']['name'])
  299. groups.extend('os_metadata_%s=%s' % item
  300. for item in attrs['metadata'].items())
  301. groups.append('os_region=' + attrs['region'])
  302. # groups specific to Mantl
  303. groups.append('role=' + attrs['metadata'].get('role', 'none'))
  304. groups.append('dc=' + attrs['consul_dc'])
  305. # groups specific to kubespray
  306. for group in attrs['metadata'].get('kubespray_groups', "").split(","):
  307. groups.append(group)
  308. return name, attrs, groups
  309. @parses('aws_instance')
  310. @calculate_mantl_vars
  311. def aws_host(resource, module_name):
  312. name = resource['primary']['attributes']['tags.Name']
  313. raw_attrs = resource['primary']['attributes']
  314. groups = []
  315. attrs = {
  316. 'ami': raw_attrs['ami'],
  317. 'availability_zone': raw_attrs['availability_zone'],
  318. 'ebs_block_device': parse_attr_list(raw_attrs, 'ebs_block_device'),
  319. 'ebs_optimized': parse_bool(raw_attrs['ebs_optimized']),
  320. 'ephemeral_block_device': parse_attr_list(raw_attrs,
  321. 'ephemeral_block_device'),
  322. 'id': raw_attrs['id'],
  323. 'key_name': raw_attrs['key_name'],
  324. 'private': parse_dict(raw_attrs, 'private',
  325. sep='_'),
  326. 'public': parse_dict(raw_attrs, 'public',
  327. sep='_'),
  328. 'root_block_device': parse_attr_list(raw_attrs, 'root_block_device'),
  329. 'security_groups': parse_list(raw_attrs, 'security_groups'),
  330. 'subnet': parse_dict(raw_attrs, 'subnet',
  331. sep='_'),
  332. 'tags': parse_dict(raw_attrs, 'tags'),
  333. 'tenancy': raw_attrs['tenancy'],
  334. 'vpc_security_group_ids': parse_list(raw_attrs,
  335. 'vpc_security_group_ids'),
  336. # ansible-specific
  337. 'ansible_ssh_port': 22,
  338. 'ansible_ssh_host': raw_attrs['public_ip'],
  339. # generic
  340. 'public_ipv4': raw_attrs['public_ip'],
  341. 'private_ipv4': raw_attrs['private_ip'],
  342. 'provider': 'aws',
  343. }
  344. # attrs specific to Ansible
  345. if 'tags.sshUser' in raw_attrs:
  346. attrs['ansible_ssh_user'] = raw_attrs['tags.sshUser']
  347. if 'tags.sshPrivateIp' in raw_attrs:
  348. attrs['ansible_ssh_host'] = raw_attrs['private_ip']
  349. # attrs specific to Mantl
  350. attrs.update({
  351. 'consul_dc': _clean_dc(attrs['tags'].get('dc', module_name)),
  352. 'role': attrs['tags'].get('role', 'none'),
  353. 'ansible_python_interpreter': attrs['tags'].get('python_bin','python')
  354. })
  355. # groups specific to Mantl
  356. groups.extend(['aws_ami=' + attrs['ami'],
  357. 'aws_az=' + attrs['availability_zone'],
  358. 'aws_key_name=' + attrs['key_name'],
  359. 'aws_tenancy=' + attrs['tenancy']])
  360. groups.extend('aws_tag_%s=%s' % item for item in attrs['tags'].items())
  361. groups.extend('aws_vpc_security_group=' + group
  362. for group in attrs['vpc_security_group_ids'])
  363. groups.extend('aws_subnet_%s=%s' % subnet
  364. for subnet in attrs['subnet'].items())
  365. # groups specific to Mantl
  366. groups.append('role=' + attrs['role'])
  367. groups.append('dc=' + attrs['consul_dc'])
  368. return name, attrs, groups
  369. @parses('google_compute_instance')
  370. @calculate_mantl_vars
  371. def gce_host(resource, module_name):
  372. name = resource['primary']['id']
  373. raw_attrs = resource['primary']['attributes']
  374. groups = []
  375. # network interfaces
  376. interfaces = parse_attr_list(raw_attrs, 'network_interface')
  377. for interface in interfaces:
  378. interface['access_config'] = parse_attr_list(interface,
  379. 'access_config')
  380. for key in interface.keys():
  381. if '.' in key:
  382. del interface[key]
  383. # general attrs
  384. attrs = {
  385. 'can_ip_forward': raw_attrs['can_ip_forward'] == 'true',
  386. 'disks': parse_attr_list(raw_attrs, 'disk'),
  387. 'machine_type': raw_attrs['machine_type'],
  388. 'metadata': parse_dict(raw_attrs, 'metadata'),
  389. 'network': parse_attr_list(raw_attrs, 'network'),
  390. 'network_interface': interfaces,
  391. 'self_link': raw_attrs['self_link'],
  392. 'service_account': parse_attr_list(raw_attrs, 'service_account'),
  393. 'tags': parse_list(raw_attrs, 'tags'),
  394. 'zone': raw_attrs['zone'],
  395. # ansible
  396. 'ansible_ssh_port': 22,
  397. 'provider': 'gce',
  398. }
  399. # attrs specific to Ansible
  400. if 'metadata.ssh_user' in raw_attrs:
  401. attrs['ansible_ssh_user'] = raw_attrs['metadata.ssh_user']
  402. # attrs specific to Mantl
  403. attrs.update({
  404. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  405. 'role': attrs['metadata'].get('role', 'none'),
  406. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  407. })
  408. try:
  409. attrs.update({
  410. 'ansible_ssh_host': interfaces[0]['access_config'][0]['nat_ip'] or interfaces[0]['access_config'][0]['assigned_nat_ip'],
  411. 'public_ipv4': interfaces[0]['access_config'][0]['nat_ip'] or interfaces[0]['access_config'][0]['assigned_nat_ip'],
  412. 'private_ipv4': interfaces[0]['address'],
  413. 'publicly_routable': True,
  414. })
  415. except (KeyError, ValueError):
  416. attrs.update({'ansible_ssh_host': '', 'publicly_routable': False})
  417. # add groups based on attrs
  418. groups.extend('gce_image=' + disk['image'] for disk in attrs['disks'])
  419. groups.append('gce_machine_type=' + attrs['machine_type'])
  420. groups.extend('gce_metadata_%s=%s' % (key, value)
  421. for (key, value) in attrs['metadata'].items()
  422. if key not in set(['sshKeys']))
  423. groups.extend('gce_tag=' + tag for tag in attrs['tags'])
  424. groups.append('gce_zone=' + attrs['zone'])
  425. if attrs['can_ip_forward']:
  426. groups.append('gce_ip_forward')
  427. if attrs['publicly_routable']:
  428. groups.append('gce_publicly_routable')
  429. # groups specific to Mantl
  430. groups.append('role=' + attrs['metadata'].get('role', 'none'))
  431. groups.append('dc=' + attrs['consul_dc'])
  432. return name, attrs, groups
  433. @parses('vsphere_virtual_machine')
  434. @calculate_mantl_vars
  435. def vsphere_host(resource, module_name):
  436. raw_attrs = resource['primary']['attributes']
  437. network_attrs = parse_dict(raw_attrs, 'network_interface')
  438. network = parse_dict(network_attrs, '0')
  439. ip_address = network.get('ipv4_address', network['ip_address'])
  440. name = raw_attrs['name']
  441. groups = []
  442. attrs = {
  443. 'id': raw_attrs['id'],
  444. 'ip_address': ip_address,
  445. 'private_ipv4': ip_address,
  446. 'public_ipv4': ip_address,
  447. 'metadata': parse_dict(raw_attrs, 'custom_configuration_parameters'),
  448. 'ansible_ssh_port': 22,
  449. 'provider': 'vsphere',
  450. }
  451. try:
  452. attrs.update({
  453. 'ansible_ssh_host': ip_address,
  454. })
  455. except (KeyError, ValueError):
  456. attrs.update({'ansible_ssh_host': '', })
  457. attrs.update({
  458. 'consul_dc': _clean_dc(attrs['metadata'].get('consul_dc', module_name)),
  459. 'role': attrs['metadata'].get('role', 'none'),
  460. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  461. })
  462. # attrs specific to Ansible
  463. if 'ssh_user' in attrs['metadata']:
  464. attrs['ansible_ssh_user'] = attrs['metadata']['ssh_user']
  465. groups.append('role=' + attrs['role'])
  466. groups.append('dc=' + attrs['consul_dc'])
  467. return name, attrs, groups
  468. @parses('azure_instance')
  469. @calculate_mantl_vars
  470. def azure_host(resource, module_name):
  471. name = resource['primary']['attributes']['name']
  472. raw_attrs = resource['primary']['attributes']
  473. groups = []
  474. attrs = {
  475. 'automatic_updates': raw_attrs['automatic_updates'],
  476. 'description': raw_attrs['description'],
  477. 'hosted_service_name': raw_attrs['hosted_service_name'],
  478. 'id': raw_attrs['id'],
  479. 'image': raw_attrs['image'],
  480. 'ip_address': raw_attrs['ip_address'],
  481. 'location': raw_attrs['location'],
  482. 'name': raw_attrs['name'],
  483. 'reverse_dns': raw_attrs['reverse_dns'],
  484. 'security_group': raw_attrs['security_group'],
  485. 'size': raw_attrs['size'],
  486. 'ssh_key_thumbprint': raw_attrs['ssh_key_thumbprint'],
  487. 'subnet': raw_attrs['subnet'],
  488. 'username': raw_attrs['username'],
  489. 'vip_address': raw_attrs['vip_address'],
  490. 'virtual_network': raw_attrs['virtual_network'],
  491. 'endpoint': parse_attr_list(raw_attrs, 'endpoint'),
  492. # ansible
  493. 'ansible_ssh_port': 22,
  494. 'ansible_ssh_user': raw_attrs['username'],
  495. 'ansible_ssh_host': raw_attrs['vip_address'],
  496. }
  497. # attrs specific to mantl
  498. attrs.update({
  499. 'consul_dc': attrs['location'].lower().replace(" ", "-"),
  500. 'role': attrs['description']
  501. })
  502. # groups specific to mantl
  503. groups.extend(['azure_image=' + attrs['image'],
  504. 'azure_location=' + attrs['location'].lower().replace(" ", "-"),
  505. 'azure_username=' + attrs['username'],
  506. 'azure_security_group=' + attrs['security_group']])
  507. # groups specific to mantl
  508. groups.append('role=' + attrs['role'])
  509. groups.append('dc=' + attrs['consul_dc'])
  510. return name, attrs, groups
  511. @parses('clc_server')
  512. @calculate_mantl_vars
  513. def clc_server(resource, module_name):
  514. raw_attrs = resource['primary']['attributes']
  515. name = raw_attrs.get('id')
  516. groups = []
  517. md = parse_dict(raw_attrs, 'metadata')
  518. attrs = {
  519. 'metadata': md,
  520. 'ansible_ssh_port': md.get('ssh_port', 22),
  521. 'ansible_ssh_user': md.get('ssh_user', 'root'),
  522. 'provider': 'clc',
  523. 'publicly_routable': False,
  524. }
  525. try:
  526. attrs.update({
  527. 'public_ipv4': raw_attrs['public_ip_address'],
  528. 'private_ipv4': raw_attrs['private_ip_address'],
  529. 'ansible_ssh_host': raw_attrs['public_ip_address'],
  530. 'publicly_routable': True,
  531. })
  532. except (KeyError, ValueError):
  533. attrs.update({
  534. 'ansible_ssh_host': raw_attrs['private_ip_address'],
  535. 'private_ipv4': raw_attrs['private_ip_address'],
  536. })
  537. attrs.update({
  538. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  539. 'role': attrs['metadata'].get('role', 'none'),
  540. })
  541. groups.append('role=' + attrs['role'])
  542. groups.append('dc=' + attrs['consul_dc'])
  543. return name, attrs, groups
  544. ## QUERY TYPES
  545. def query_host(hosts, target):
  546. for name, attrs, _ in hosts:
  547. if name == target:
  548. return attrs
  549. return {}
  550. def query_list(hosts):
  551. groups = defaultdict(dict)
  552. meta = {}
  553. for name, attrs, hostgroups in hosts:
  554. for group in set(hostgroups):
  555. groups[group].setdefault('hosts', [])
  556. groups[group]['hosts'].append(name)
  557. meta[name] = attrs
  558. groups['_meta'] = {'hostvars': meta}
  559. return groups
  560. def query_hostfile(hosts):
  561. out = ['## begin hosts generated by terraform.py ##']
  562. out.extend(
  563. '{}\t{}'.format(attrs['ansible_ssh_host'].ljust(16), name)
  564. for name, attrs, _ in hosts
  565. )
  566. out.append('## end hosts generated by terraform.py ##')
  567. return '\n'.join(out)
  568. def main():
  569. parser = argparse.ArgumentParser(
  570. __file__, __doc__,
  571. formatter_class=argparse.ArgumentDefaultsHelpFormatter, )
  572. modes = parser.add_mutually_exclusive_group(required=True)
  573. modes.add_argument('--list',
  574. action='store_true',
  575. help='list all variables')
  576. modes.add_argument('--host', help='list variables for a single host')
  577. modes.add_argument('--version',
  578. action='store_true',
  579. help='print version and exit')
  580. modes.add_argument('--hostfile',
  581. action='store_true',
  582. help='print hosts as a /etc/hosts snippet')
  583. parser.add_argument('--pretty',
  584. action='store_true',
  585. help='pretty-print output JSON')
  586. parser.add_argument('--nometa',
  587. action='store_true',
  588. help='with --list, exclude hostvars')
  589. default_root = os.environ.get('TERRAFORM_STATE_ROOT',
  590. os.path.abspath(os.path.join(os.path.dirname(__file__),
  591. '..', '..', )))
  592. parser.add_argument('--root',
  593. default=default_root,
  594. help='custom root to search for `.tfstate`s in')
  595. args = parser.parse_args()
  596. if args.version:
  597. print('%s %s' % (__file__, VERSION))
  598. parser.exit()
  599. hosts = iterhosts(iterresources(tfstates(args.root)))
  600. if args.list:
  601. output = query_list(hosts)
  602. if args.nometa:
  603. del output['_meta']
  604. print(json.dumps(output, indent=4 if args.pretty else None))
  605. elif args.host:
  606. output = query_host(hosts, args.host)
  607. print(json.dumps(output, indent=4 if args.pretty else None))
  608. elif args.hostfile:
  609. output = query_hostfile(hosts)
  610. print(output)
  611. parser.exit()
  612. if __name__ == '__main__':
  613. main()