123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- #!/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 MultiEc2(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(),'multi_ec2.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 = MultiEc2()
- #print mi.result
- pp = pprint.PrettyPrinter(indent=2)
- pp.pprint(mi.result)
|