Created
July 15, 2021 09:06
-
-
Save imcitius/407d9be69dafad6dcfc9a6b257a1b8f2 to your computer and use it in GitHub Desktop.
Ansible dynamic inventory script for terraform
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from __future__ import print_function | |
import base64 | |
import os | |
import sys | |
import json | |
from collections import OrderedDict | |
import requests | |
from requests.auth import HTTPBasicAuth | |
import re | |
import traceback | |
tf_state_path = os.environ['TF_STATE_PATH'] | |
project = os.environ['ANSIBLE_PROJECT'] | |
if tf_state_path == '' or project == '': | |
sys.stderr.write('TF state path or ansible project name not specified\n') | |
sys.exit(1) | |
def _init_inventory(): | |
return OrderedDict({ | |
"all": { | |
"hosts": [], | |
"vars": {}, | |
"children": [] | |
}, | |
"_meta": { | |
"hostvars": {} | |
} | |
}) | |
def _is_ip_private(ip): | |
# https://en.wikipedia.org/wiki/Private_network | |
priv_lo = re.compile("^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$") | |
priv_24 = re.compile("^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$") | |
priv_20 = re.compile("^192\.168\.\d{1,3}.\d{1,3}$") | |
priv_16 = re.compile("^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$") | |
res = priv_lo.match(ip) or priv_24.match(ip) or priv_20.match(ip) or priv_16.match(ip) | |
return res is not None | |
def parse_hosts(): | |
resources = tfstate["resources"] | |
inv = _init_inventory() | |
for resource in resources: | |
if resource["mode"] == "managed" and resource["type"] == "vsphere_virtual_machine": | |
group_name = resource["module"].replace("module.", "") | |
normalized_group_name = resource["module"].replace("module.", "").replace("_", "-") | |
if normalized_group_name not in inv['all']['children']: | |
inv['all']['children'].append(normalized_group_name) | |
if normalized_group_name not in inv: | |
inv[normalized_group_name] = {} | |
inv[normalized_group_name]['hosts'] = [] | |
inv[normalized_group_name]['vars'] = {} | |
for instance in resource['instances']: | |
if "attributes" in instance: | |
attrs = instance['attributes'] | |
else: | |
raise KeyError("instance has no attributes, module %s, index key: %s" % (module, instance['index_key'])) | |
if ('default_ip_address' in attrs) and (_is_ip_private(attrs['default_ip_address'])): | |
host = attrs['default_ip_address'] | |
elif ('guest_ip_addresses.0' in attrs) and (_is_ip_private(attrs['guest_ip_addresses.0'])): | |
host = attrs['guest_ip_addresses.0'] | |
elif ('guest_ip_addresses.1' in attrs) and (_is_ip_private(attrs['guest_ip_addresses.1'])): | |
host = attrs['guest_ip_addresses.1'] | |
# if host not in inv['all']['hosts']: | |
# inv['all']['hosts'].append(host) | |
if host not in inv[normalized_group_name]['hosts']: | |
inv[normalized_group_name]['hosts'].append(host) | |
if host not in inv['_meta']['hostvars']: | |
inv['_meta']['hostvars'][host] = {} | |
inv['_meta']['hostvars'][host]['hostname'] = attrs['name'] | |
inv['_meta']['hostvars'][host]['group'] = normalized_group_name | |
return inv | |
def _processing(tfstate, inventory): | |
if 'resources' not in tfstate: | |
raise KeyError("resources not found in tfstate") | |
resources = tfstate["resources"] | |
outputs = tfstate["outputs"] | |
inventory = parse_hosts() | |
inventory['all']['vars']['project'] = project | |
# inventory['all']['hosts'] = parse_hosts()['all']['hosts'] | |
for group in outputs: | |
normalized_group_name = group.replace("_", "-") | |
# we may have outputs for groups withous VM's | |
if normalized_group_name in inventory: | |
inventory[normalized_group_name]['vars'] = outputs[group]['value']['meta'] | |
return inventory | |
def get_tfstate(): | |
# trying to get state from http backend | |
url = 'http://' + 'terraform-state-store.service.ks-1.consul' + '/v1/state/' + project | |
webUser = tf_state_path.split('/')[2] | |
webPass = 'any' | |
r = requests.get(url, auth=HTTPBasicAuth(webUser, webPass)) | |
if r.status_code == 200: | |
# looks like we get it | |
return r.json() | |
# trying to get from consul if http backend failed | |
url = 'http://' + 'consul.service.infra1.consul' + ':8500/v1/kv/' + tf_state_path + '%3A' + project | |
r = requests.get(url) | |
return json.loads(base64.b64decode(r.json()[0]['Value'])) | |
try: | |
tfstate = get_tfstate() | |
inventory = _processing(tfstate, _init_inventory()) | |
sys.stdout.write(json.dumps(inventory, indent=2)) | |
except Exception as e: | |
print(str(e), file=sys.stderr) | |
print(traceback.format_exc()) | |
sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment