summaryrefslogtreecommitdiffstats
path: root/inventory/multi_ec2.py
diff options
context:
space:
mode:
Diffstat (limited to 'inventory/multi_ec2.py')
-rwxr-xr-xinventory/multi_ec2.py183
1 files changed, 183 insertions, 0 deletions
diff --git a/inventory/multi_ec2.py b/inventory/multi_ec2.py
new file mode 100755
index 000000000..c13a0295c
--- /dev/null
+++ b/inventory/multi_ec2.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+
+from time import time
+import argparse
+import yaml
+import os
+import sys
+import pdb
+import subprocess
+import json
+import pprint
+
+
+class MetaInventory(object):
+
+ def __init__(self):
+ self.config = None
+ self.results = {}
+ self.result = {}
+ self.cache_path_cache = os.path.expanduser('~/.ansible/tmp/meta-inventory.cache')
+
+ self.parse_cli_args()
+
+ # load yaml
+ self.load_yaml_config()
+
+ # if its a host query, fetch and do not cache
+ if self.args.host:
+ self.get_inventory()
+ elif not self.is_cache_valid():
+ # go fetch the inventories and cache them if cache is expired
+ self.get_inventory()
+ self.write_to_cache()
+ else:
+ # get data from disk
+ self.get_inventory_from_cache()
+
+ def load_yaml_config(self,conf_file=os.path.join(os.getcwd(),'meta.yaml')):
+ """Load a yaml config file with credentials to query the
+ respective cloud for inventory.
+ """
+ config = None
+ with open(conf_file) as conf:
+ self.config = yaml.safe_load(conf)
+
+ def get_provider_tags(self,provider, env={}):
+ """Call <provider> and query all of the tags that are usuable
+ by ansible. If environment is empty use the default env.
+ """
+ if not env:
+ env = os.environ
+
+ # check to see if provider exists
+ if not os.path.isfile(os.path.join(os.getcwd(),provider)):
+ raise RuntimeError("Unkown provider: %s" % provider)
+
+ cmds = [provider]
+ if self.args.host:
+ cmds.append("--host")
+ cmds.append(self.args.host)
+ else:
+ cmds.append('--list')
+
+ cmds.append('--refresh-cache')
+
+ return subprocess.Popen(cmds, stderr=subprocess.PIPE, \
+ stdout=subprocess.PIPE, env=env)
+ def get_inventory(self):
+ """Create the subprocess to fetch tags from a provider.
+ Host query:
+ Query to return a specific host. If > 1 queries have
+ results then fail.
+
+ List query:
+ Query all of the different clouds for their tags. Once completed
+ store all of their results into one merged updated hash.
+ """
+ processes = {}
+ for account in self.config['clouds']:
+ env = account['env_vars']
+ name = account['name']
+ provider = account['provider']
+ processes[name] = self.get_provider_tags(provider, env)
+
+ # for each process collect stdout when its available
+ all_results = []
+ for name, process in processes.items():
+ out, err = process.communicate()
+ all_results.append({
+ "name": name,
+ "out": out.strip(),
+ "err": err.strip(),
+ "code": process.returncode
+ })
+
+ if not self.args.host:
+ # For any non-zero, raise an error on it
+ for result in all_results:
+ if result['code'] != 0:
+ raise RuntimeError(result['err'])
+ else:
+ self.results[result['name']] = json.loads(result['out'])
+ self.merge()
+ else:
+ # For any 0 result, return it
+ count = 0
+ for results in all_results:
+ if results['code'] == 0 and results['err'] == '' and results['out'] != '{}':
+ self.result = json.loads(out)
+ count += 1
+ if count > 1:
+ raise RuntimeError("Found > 1 results for --host %s. \
+ This is an invalid state." % self.args.host)
+
+ def merge(self):
+ """Merge the results into a single hash. Duplicate keys are placed
+ into a list.
+ """
+ for name, cloud_result in self.results.items():
+ for k,v in cloud_result.items():
+ if self.result.has_key(k):
+ # need to combine into a list
+ if isinstance(self.result[k], list):
+ self.result[k].append(v)
+ else:
+ self.result[k] = [self.result[k],v]
+ else:
+ self.result[k] = [v]
+
+ self.result = self.json_format_dict(self.result)
+
+ def is_cache_valid(self):
+ ''' Determines if the cache files have expired, or if it is still valid '''
+
+ if os.path.isfile(self.cache_path_cache):
+ mod_time = os.path.getmtime(self.cache_path_cache)
+ current_time = time()
+ if (mod_time + self.config['cache_max_age']) > current_time:
+ #if os.path.isfile(self.cache_path_index):
+ return True
+
+ return False
+
+ def parse_cli_args(self):
+ ''' Command line argument processing '''
+
+ parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on a provider')
+ parser.add_argument('--list', action='store_true', default=True,
+ help='List instances (default: True)')
+ parser.add_argument('--host', action='store',
+ help='Get all the variables about a specific instance')
+ self.args = parser.parse_args()
+
+ def write_to_cache(self):
+ ''' Writes data in JSON format to a file '''
+
+ json_data = self.json_format_dict(self.result, True)
+ with open(self.cache_path_cache, 'w') as cache:
+ cache.write(json_data)
+
+ def get_inventory_from_cache(self):
+ ''' Reads the inventory from the cache file and returns it as a JSON
+ object '''
+
+ with open(self.cache_path_cache, 'r') as cache:
+ self.result = json.loads(cache.read())
+
+ def json_format_dict(self, data, pretty=False):
+ ''' Converts a dict to a JSON object and dumps it as a formatted
+ string '''
+
+ if pretty:
+ return json.dumps(data, sort_keys=True, indent=2)
+ else:
+ return json.dumps(data)
+
+
+if __name__ == "__main__":
+ mi = MetaInventory()
+ #print mi.result
+ pp = pprint.PrettyPrinter(indent=2)
+ pp.pprint(mi.result)
+