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.

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