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.

793 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. 'access_ip': raw_attrs['access_ip_v4'],
  266. 'ip': raw_attrs['network.0.fixed_ip_v4'],
  267. 'flavor': parse_dict(raw_attrs, 'flavor',
  268. sep='_'),
  269. 'id': raw_attrs['id'],
  270. 'image': parse_dict(raw_attrs, 'image',
  271. sep='_'),
  272. 'key_pair': raw_attrs['key_pair'],
  273. 'metadata': parse_dict(raw_attrs, 'metadata'),
  274. 'network': parse_attr_list(raw_attrs, 'network'),
  275. 'region': raw_attrs.get('region', ''),
  276. 'security_groups': parse_list(raw_attrs, 'security_groups'),
  277. # ansible
  278. 'ansible_ssh_port': 22,
  279. # workaround for an OpenStack bug where hosts have a different domain
  280. # after they're restarted
  281. 'host_domain': 'novalocal',
  282. 'use_host_domain': True,
  283. # generic
  284. 'public_ipv4': raw_attrs['access_ip_v4'],
  285. 'private_ipv4': raw_attrs['access_ip_v4'],
  286. 'provider': 'openstack',
  287. }
  288. if 'floating_ip' in raw_attrs:
  289. attrs['private_ipv4'] = raw_attrs['network.0.fixed_ip_v4']
  290. try:
  291. attrs.update({
  292. 'ansible_ssh_host': raw_attrs['access_ip_v4'],
  293. 'publicly_routable': True,
  294. })
  295. except (KeyError, ValueError):
  296. attrs.update({'ansible_ssh_host': '', 'publicly_routable': False})
  297. # Handling of floating IPs has changed: https://github.com/terraform-providers/terraform-provider-openstack/blob/master/CHANGELOG.md#010-june-21-2017
  298. # attrs specific to Ansible
  299. if 'metadata.ssh_user' in raw_attrs:
  300. attrs['ansible_ssh_user'] = raw_attrs['metadata.ssh_user']
  301. if 'volume.#' in raw_attrs.keys() and int(raw_attrs['volume.#']) > 0:
  302. device_index = 1
  303. for key, value in raw_attrs.items():
  304. match = re.search("^volume.*.device$", key)
  305. if match:
  306. attrs['disk_volume_device_'+str(device_index)] = value
  307. device_index += 1
  308. # attrs specific to Mantl
  309. attrs.update({
  310. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  311. 'role': attrs['metadata'].get('role', 'none'),
  312. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  313. })
  314. # add groups based on attrs
  315. groups.append('os_image=' + attrs['image']['name'])
  316. groups.append('os_flavor=' + attrs['flavor']['name'])
  317. groups.extend('os_metadata_%s=%s' % item
  318. for item in attrs['metadata'].items())
  319. groups.append('os_region=' + attrs['region'])
  320. # groups specific to Mantl
  321. groups.append('role=' + attrs['metadata'].get('role', 'none'))
  322. groups.append('dc=' + attrs['consul_dc'])
  323. # groups specific to kubespray
  324. for group in attrs['metadata'].get('kubespray_groups', "").split(","):
  325. groups.append(group)
  326. return name, attrs, groups
  327. @parses('aws_instance')
  328. @calculate_mantl_vars
  329. def aws_host(resource, module_name):
  330. name = resource['primary']['attributes']['tags.Name']
  331. raw_attrs = resource['primary']['attributes']
  332. groups = []
  333. attrs = {
  334. 'ami': raw_attrs['ami'],
  335. 'availability_zone': raw_attrs['availability_zone'],
  336. 'ebs_block_device': parse_attr_list(raw_attrs, 'ebs_block_device'),
  337. 'ebs_optimized': parse_bool(raw_attrs['ebs_optimized']),
  338. 'ephemeral_block_device': parse_attr_list(raw_attrs,
  339. 'ephemeral_block_device'),
  340. 'id': raw_attrs['id'],
  341. 'key_name': raw_attrs['key_name'],
  342. 'private': parse_dict(raw_attrs, 'private',
  343. sep='_'),
  344. 'public': parse_dict(raw_attrs, 'public',
  345. sep='_'),
  346. 'root_block_device': parse_attr_list(raw_attrs, 'root_block_device'),
  347. 'security_groups': parse_list(raw_attrs, 'security_groups'),
  348. 'subnet': parse_dict(raw_attrs, 'subnet',
  349. sep='_'),
  350. 'tags': parse_dict(raw_attrs, 'tags'),
  351. 'tenancy': raw_attrs['tenancy'],
  352. 'vpc_security_group_ids': parse_list(raw_attrs,
  353. 'vpc_security_group_ids'),
  354. # ansible-specific
  355. 'ansible_ssh_port': 22,
  356. 'ansible_ssh_host': raw_attrs['public_ip'],
  357. # generic
  358. 'public_ipv4': raw_attrs['public_ip'],
  359. 'private_ipv4': raw_attrs['private_ip'],
  360. 'provider': 'aws',
  361. }
  362. # attrs specific to Ansible
  363. if 'tags.sshUser' in raw_attrs:
  364. attrs['ansible_ssh_user'] = raw_attrs['tags.sshUser']
  365. if 'tags.sshPrivateIp' in raw_attrs:
  366. attrs['ansible_ssh_host'] = raw_attrs['private_ip']
  367. # attrs specific to Mantl
  368. attrs.update({
  369. 'consul_dc': _clean_dc(attrs['tags'].get('dc', module_name)),
  370. 'role': attrs['tags'].get('role', 'none'),
  371. 'ansible_python_interpreter': attrs['tags'].get('python_bin','python')
  372. })
  373. # groups specific to Mantl
  374. groups.extend(['aws_ami=' + attrs['ami'],
  375. 'aws_az=' + attrs['availability_zone'],
  376. 'aws_key_name=' + attrs['key_name'],
  377. 'aws_tenancy=' + attrs['tenancy']])
  378. groups.extend('aws_tag_%s=%s' % item for item in attrs['tags'].items())
  379. groups.extend('aws_vpc_security_group=' + group
  380. for group in attrs['vpc_security_group_ids'])
  381. groups.extend('aws_subnet_%s=%s' % subnet
  382. for subnet in attrs['subnet'].items())
  383. # groups specific to Mantl
  384. groups.append('role=' + attrs['role'])
  385. groups.append('dc=' + attrs['consul_dc'])
  386. return name, attrs, groups
  387. @parses('google_compute_instance')
  388. @calculate_mantl_vars
  389. def gce_host(resource, module_name):
  390. name = resource['primary']['id']
  391. raw_attrs = resource['primary']['attributes']
  392. groups = []
  393. # network interfaces
  394. interfaces = parse_attr_list(raw_attrs, 'network_interface')
  395. for interface in interfaces:
  396. interface['access_config'] = parse_attr_list(interface,
  397. 'access_config')
  398. for key in interface.keys():
  399. if '.' in key:
  400. del interface[key]
  401. # general attrs
  402. attrs = {
  403. 'can_ip_forward': raw_attrs['can_ip_forward'] == 'true',
  404. 'disks': parse_attr_list(raw_attrs, 'disk'),
  405. 'machine_type': raw_attrs['machine_type'],
  406. 'metadata': parse_dict(raw_attrs, 'metadata'),
  407. 'network': parse_attr_list(raw_attrs, 'network'),
  408. 'network_interface': interfaces,
  409. 'self_link': raw_attrs['self_link'],
  410. 'service_account': parse_attr_list(raw_attrs, 'service_account'),
  411. 'tags': parse_list(raw_attrs, 'tags'),
  412. 'zone': raw_attrs['zone'],
  413. # ansible
  414. 'ansible_ssh_port': 22,
  415. 'provider': 'gce',
  416. }
  417. # attrs specific to Ansible
  418. if 'metadata.ssh_user' in raw_attrs:
  419. attrs['ansible_ssh_user'] = raw_attrs['metadata.ssh_user']
  420. # attrs specific to Mantl
  421. attrs.update({
  422. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  423. 'role': attrs['metadata'].get('role', 'none'),
  424. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  425. })
  426. try:
  427. attrs.update({
  428. 'ansible_ssh_host': interfaces[0]['access_config'][0]['nat_ip'] or interfaces[0]['access_config'][0]['assigned_nat_ip'],
  429. 'public_ipv4': interfaces[0]['access_config'][0]['nat_ip'] or interfaces[0]['access_config'][0]['assigned_nat_ip'],
  430. 'private_ipv4': interfaces[0]['address'],
  431. 'publicly_routable': True,
  432. })
  433. except (KeyError, ValueError):
  434. attrs.update({'ansible_ssh_host': '', 'publicly_routable': False})
  435. # add groups based on attrs
  436. groups.extend('gce_image=' + disk['image'] for disk in attrs['disks'])
  437. groups.append('gce_machine_type=' + attrs['machine_type'])
  438. groups.extend('gce_metadata_%s=%s' % (key, value)
  439. for (key, value) in attrs['metadata'].items()
  440. if key not in set(['sshKeys']))
  441. groups.extend('gce_tag=' + tag for tag in attrs['tags'])
  442. groups.append('gce_zone=' + attrs['zone'])
  443. if attrs['can_ip_forward']:
  444. groups.append('gce_ip_forward')
  445. if attrs['publicly_routable']:
  446. groups.append('gce_publicly_routable')
  447. # groups specific to Mantl
  448. groups.append('role=' + attrs['metadata'].get('role', 'none'))
  449. groups.append('dc=' + attrs['consul_dc'])
  450. return name, attrs, groups
  451. @parses('vsphere_virtual_machine')
  452. @calculate_mantl_vars
  453. def vsphere_host(resource, module_name):
  454. raw_attrs = resource['primary']['attributes']
  455. network_attrs = parse_dict(raw_attrs, 'network_interface')
  456. network = parse_dict(network_attrs, '0')
  457. ip_address = network.get('ipv4_address', network['ip_address'])
  458. name = raw_attrs['name']
  459. groups = []
  460. attrs = {
  461. 'id': raw_attrs['id'],
  462. 'ip_address': ip_address,
  463. 'private_ipv4': ip_address,
  464. 'public_ipv4': ip_address,
  465. 'metadata': parse_dict(raw_attrs, 'custom_configuration_parameters'),
  466. 'ansible_ssh_port': 22,
  467. 'provider': 'vsphere',
  468. }
  469. try:
  470. attrs.update({
  471. 'ansible_ssh_host': ip_address,
  472. })
  473. except (KeyError, ValueError):
  474. attrs.update({'ansible_ssh_host': '', })
  475. attrs.update({
  476. 'consul_dc': _clean_dc(attrs['metadata'].get('consul_dc', module_name)),
  477. 'role': attrs['metadata'].get('role', 'none'),
  478. 'ansible_python_interpreter': attrs['metadata'].get('python_bin','python')
  479. })
  480. # attrs specific to Ansible
  481. if 'ssh_user' in attrs['metadata']:
  482. attrs['ansible_ssh_user'] = attrs['metadata']['ssh_user']
  483. groups.append('role=' + attrs['role'])
  484. groups.append('dc=' + attrs['consul_dc'])
  485. return name, attrs, groups
  486. @parses('azure_instance')
  487. @calculate_mantl_vars
  488. def azure_host(resource, module_name):
  489. name = resource['primary']['attributes']['name']
  490. raw_attrs = resource['primary']['attributes']
  491. groups = []
  492. attrs = {
  493. 'automatic_updates': raw_attrs['automatic_updates'],
  494. 'description': raw_attrs['description'],
  495. 'hosted_service_name': raw_attrs['hosted_service_name'],
  496. 'id': raw_attrs['id'],
  497. 'image': raw_attrs['image'],
  498. 'ip_address': raw_attrs['ip_address'],
  499. 'location': raw_attrs['location'],
  500. 'name': raw_attrs['name'],
  501. 'reverse_dns': raw_attrs['reverse_dns'],
  502. 'security_group': raw_attrs['security_group'],
  503. 'size': raw_attrs['size'],
  504. 'ssh_key_thumbprint': raw_attrs['ssh_key_thumbprint'],
  505. 'subnet': raw_attrs['subnet'],
  506. 'username': raw_attrs['username'],
  507. 'vip_address': raw_attrs['vip_address'],
  508. 'virtual_network': raw_attrs['virtual_network'],
  509. 'endpoint': parse_attr_list(raw_attrs, 'endpoint'),
  510. # ansible
  511. 'ansible_ssh_port': 22,
  512. 'ansible_ssh_user': raw_attrs['username'],
  513. 'ansible_ssh_host': raw_attrs['vip_address'],
  514. }
  515. # attrs specific to mantl
  516. attrs.update({
  517. 'consul_dc': attrs['location'].lower().replace(" ", "-"),
  518. 'role': attrs['description']
  519. })
  520. # groups specific to mantl
  521. groups.extend(['azure_image=' + attrs['image'],
  522. 'azure_location=' + attrs['location'].lower().replace(" ", "-"),
  523. 'azure_username=' + attrs['username'],
  524. 'azure_security_group=' + attrs['security_group']])
  525. # groups specific to mantl
  526. groups.append('role=' + attrs['role'])
  527. groups.append('dc=' + attrs['consul_dc'])
  528. return name, attrs, groups
  529. @parses('clc_server')
  530. @calculate_mantl_vars
  531. def clc_server(resource, module_name):
  532. raw_attrs = resource['primary']['attributes']
  533. name = raw_attrs.get('id')
  534. groups = []
  535. md = parse_dict(raw_attrs, 'metadata')
  536. attrs = {
  537. 'metadata': md,
  538. 'ansible_ssh_port': md.get('ssh_port', 22),
  539. 'ansible_ssh_user': md.get('ssh_user', 'root'),
  540. 'provider': 'clc',
  541. 'publicly_routable': False,
  542. }
  543. try:
  544. attrs.update({
  545. 'public_ipv4': raw_attrs['public_ip_address'],
  546. 'private_ipv4': raw_attrs['private_ip_address'],
  547. 'ansible_ssh_host': raw_attrs['public_ip_address'],
  548. 'publicly_routable': True,
  549. })
  550. except (KeyError, ValueError):
  551. attrs.update({
  552. 'ansible_ssh_host': raw_attrs['private_ip_address'],
  553. 'private_ipv4': raw_attrs['private_ip_address'],
  554. })
  555. attrs.update({
  556. 'consul_dc': _clean_dc(attrs['metadata'].get('dc', module_name)),
  557. 'role': attrs['metadata'].get('role', 'none'),
  558. })
  559. groups.append('role=' + attrs['role'])
  560. groups.append('dc=' + attrs['consul_dc'])
  561. return name, attrs, groups
  562. def iter_host_ips(hosts, ips):
  563. '''Update hosts that have an entry in the floating IP list'''
  564. for host in hosts:
  565. host_id = host[1]['id']
  566. if host_id in ips:
  567. ip = ips[host_id]
  568. host[1].update({
  569. 'access_ip_v4': ip,
  570. 'access_ip': ip,
  571. 'public_ipv4': ip,
  572. 'ansible_ssh_host': ip,
  573. })
  574. yield host
  575. ## QUERY TYPES
  576. def query_host(hosts, target):
  577. for name, attrs, _ in hosts:
  578. if name == target:
  579. return attrs
  580. return {}
  581. def query_list(hosts):
  582. groups = defaultdict(dict)
  583. meta = {}
  584. for name, attrs, hostgroups in hosts:
  585. for group in set(hostgroups):
  586. # Ansible 2.6.2 stopped supporting empty group names: https://github.com/ansible/ansible/pull/42584/commits/d4cd474b42ed23d8f8aabb2a7f84699673852eaf
  587. # Empty group name defaults to "all" in Ansible < 2.6.2 so we alter empty group names to "all"
  588. if not group: group = "all"
  589. groups[group].setdefault('hosts', [])
  590. groups[group]['hosts'].append(name)
  591. meta[name] = attrs
  592. groups['_meta'] = {'hostvars': meta}
  593. return groups
  594. def query_hostfile(hosts):
  595. out = ['## begin hosts generated by terraform.py ##']
  596. out.extend(
  597. '{}\t{}'.format(attrs['ansible_ssh_host'].ljust(16), name)
  598. for name, attrs, _ in hosts
  599. )
  600. out.append('## end hosts generated by terraform.py ##')
  601. return '\n'.join(out)
  602. def main():
  603. parser = argparse.ArgumentParser(
  604. __file__, __doc__,
  605. formatter_class=argparse.ArgumentDefaultsHelpFormatter, )
  606. modes = parser.add_mutually_exclusive_group(required=True)
  607. modes.add_argument('--list',
  608. action='store_true',
  609. help='list all variables')
  610. modes.add_argument('--host', help='list variables for a single host')
  611. modes.add_argument('--version',
  612. action='store_true',
  613. help='print version and exit')
  614. modes.add_argument('--hostfile',
  615. action='store_true',
  616. help='print hosts as a /etc/hosts snippet')
  617. parser.add_argument('--pretty',
  618. action='store_true',
  619. help='pretty-print output JSON')
  620. parser.add_argument('--nometa',
  621. action='store_true',
  622. help='with --list, exclude hostvars')
  623. default_root = os.environ.get('TERRAFORM_STATE_ROOT',
  624. os.path.abspath(os.path.join(os.path.dirname(__file__),
  625. '..', '..', )))
  626. parser.add_argument('--root',
  627. default=default_root,
  628. help='custom root to search for `.tfstate`s in')
  629. args = parser.parse_args()
  630. if args.version:
  631. print('%s %s' % (__file__, VERSION))
  632. parser.exit()
  633. hosts = iterhosts(iterresources(tfstates(args.root)))
  634. # Perform a second pass on the file to pick up floating_ip entries to update the ip address of referenced hosts
  635. ips = dict(iterips(iterresources(tfstates(args.root))))
  636. if ips:
  637. hosts = iter_host_ips(hosts, ips)
  638. if args.list:
  639. output = query_list(hosts)
  640. if args.nometa:
  641. del output['_meta']
  642. print(json.dumps(output, indent=4 if args.pretty else None))
  643. elif args.host:
  644. output = query_host(hosts, args.host)
  645. print(json.dumps(output, indent=4 if args.pretty else None))
  646. elif args.hostfile:
  647. output = query_hostfile(hosts)
  648. print(output)
  649. parser.exit()
  650. if __name__ == '__main__':
  651. main()