github.com/karalabe/go-ethereum@v0.8.5/tests/files/ansible/ec2.py (about)

     1  #!/usr/bin/env python
     2  
     3  '''
     4  EC2 external inventory script
     5  =================================
     6  
     7  Generates inventory that Ansible can understand by making API request to
     8  AWS EC2 using the Boto library.
     9  
    10  NOTE: This script assumes Ansible is being executed where the environment
    11  variables needed for Boto have already been set:
    12      export AWS_ACCESS_KEY_ID='AK123'
    13      export AWS_SECRET_ACCESS_KEY='abc123'
    14  
    15  This script also assumes there is an ec2.ini file alongside it.  To specify a
    16  different path to ec2.ini, define the EC2_INI_PATH environment variable:
    17  
    18      export EC2_INI_PATH=/path/to/my_ec2.ini
    19  
    20  If you're using eucalyptus you need to set the above variables and
    21  you need to define:
    22  
    23      export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus
    24  
    25  For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html
    26  
    27  When run against a specific host, this script returns the following variables:
    28   - ec2_ami_launch_index
    29   - ec2_architecture
    30   - ec2_association
    31   - ec2_attachTime
    32   - ec2_attachment
    33   - ec2_attachmentId
    34   - ec2_client_token
    35   - ec2_deleteOnTermination
    36   - ec2_description
    37   - ec2_deviceIndex
    38   - ec2_dns_name
    39   - ec2_eventsSet
    40   - ec2_group_name
    41   - ec2_hypervisor
    42   - ec2_id
    43   - ec2_image_id
    44   - ec2_instanceState
    45   - ec2_instance_type
    46   - ec2_ipOwnerId
    47   - ec2_ip_address
    48   - ec2_item
    49   - ec2_kernel
    50   - ec2_key_name
    51   - ec2_launch_time
    52   - ec2_monitored
    53   - ec2_monitoring
    54   - ec2_networkInterfaceId
    55   - ec2_ownerId
    56   - ec2_persistent
    57   - ec2_placement
    58   - ec2_platform
    59   - ec2_previous_state
    60   - ec2_private_dns_name
    61   - ec2_private_ip_address
    62   - ec2_publicIp
    63   - ec2_public_dns_name
    64   - ec2_ramdisk
    65   - ec2_reason
    66   - ec2_region
    67   - ec2_requester_id
    68   - ec2_root_device_name
    69   - ec2_root_device_type
    70   - ec2_security_group_ids
    71   - ec2_security_group_names
    72   - ec2_shutdown_state
    73   - ec2_sourceDestCheck
    74   - ec2_spot_instance_request_id
    75   - ec2_state
    76   - ec2_state_code
    77   - ec2_state_reason
    78   - ec2_status
    79   - ec2_subnet_id
    80   - ec2_tenancy
    81   - ec2_virtualization_type
    82   - ec2_vpc_id
    83  
    84  These variables are pulled out of a boto.ec2.instance object. There is a lack of
    85  consistency with variable spellings (camelCase and underscores) since this
    86  just loops through all variables the object exposes. It is preferred to use the
    87  ones with underscores when multiple exist.
    88  
    89  In addition, if an instance has AWS Tags associated with it, each tag is a new
    90  variable named:
    91   - ec2_tag_[Key] = [Value]
    92  
    93  Security groups are comma-separated in 'ec2_security_group_ids' and
    94  'ec2_security_group_names'.
    95  '''
    96  
    97  # (c) 2012, Peter Sankauskas
    98  #
    99  # This file is part of Ansible,
   100  #
   101  # Ansible is free software: you can redistribute it and/or modify
   102  # it under the terms of the GNU General Public License as published by
   103  # the Free Software Foundation, either version 3 of the License, or
   104  # (at your option) any later version.
   105  #
   106  # Ansible is distributed in the hope that it will be useful,
   107  # but WITHOUT ANY WARRANTY; without even the implied warranty of
   108  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   109  # GNU General Public License for more details.
   110  #
   111  # You should have received a copy of the GNU General Public License
   112  # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
   113  
   114  ######################################################################
   115  
   116  import sys
   117  import os
   118  import argparse
   119  import re
   120  from time import time
   121  import boto
   122  from boto import ec2
   123  from boto import rds
   124  from boto import route53
   125  import ConfigParser
   126  from collections import defaultdict
   127  
   128  try:
   129      import json
   130  except ImportError:
   131      import simplejson as json
   132  
   133  
   134  class Ec2Inventory(object):
   135      def _empty_inventory(self):
   136          return {"_meta" : {"hostvars" : {}}}
   137  
   138      def __init__(self):
   139          ''' Main execution path '''
   140  
   141          # Inventory grouped by instance IDs, tags, security groups, regions,
   142          # and availability zones
   143          self.inventory = self._empty_inventory()
   144  
   145          # Index of hostname (address) to instance ID
   146          self.index = {}
   147  
   148          # Read settings and parse CLI arguments
   149          self.read_settings()
   150          self.parse_cli_args()
   151  
   152          # Cache
   153          if self.args.refresh_cache:
   154              self.do_api_calls_update_cache()
   155          elif not self.is_cache_valid():
   156              self.do_api_calls_update_cache()
   157  
   158          # Data to print
   159          if self.args.host:
   160              data_to_print = self.get_host_info()
   161  
   162          elif self.args.list:
   163              # Display list of instances for inventory
   164              if self.inventory == self._empty_inventory():
   165                  data_to_print = self.get_inventory_from_cache()
   166              else:
   167                  data_to_print = self.json_format_dict(self.inventory, True)
   168  
   169          print data_to_print
   170  
   171  
   172      def is_cache_valid(self):
   173          ''' Determines if the cache files have expired, or if it is still valid '''
   174  
   175          if os.path.isfile(self.cache_path_cache):
   176              mod_time = os.path.getmtime(self.cache_path_cache)
   177              current_time = time()
   178              if (mod_time + self.cache_max_age) > current_time:
   179                  if os.path.isfile(self.cache_path_index):
   180                      return True
   181  
   182          return False
   183  
   184  
   185      def read_settings(self):
   186          ''' Reads the settings from the ec2.ini file '''
   187  
   188          config = ConfigParser.SafeConfigParser()
   189          ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini')
   190          ec2_ini_path = os.environ.get('EC2_INI_PATH', ec2_default_ini_path)
   191          config.read(ec2_ini_path)
   192  
   193          # is eucalyptus?
   194          self.eucalyptus_host = None
   195          self.eucalyptus = False
   196          if config.has_option('ec2', 'eucalyptus'):
   197              self.eucalyptus = config.getboolean('ec2', 'eucalyptus')
   198          if self.eucalyptus and config.has_option('ec2', 'eucalyptus_host'):
   199              self.eucalyptus_host = config.get('ec2', 'eucalyptus_host')
   200  
   201          # Regions
   202          self.regions = []
   203          configRegions = config.get('ec2', 'regions')
   204          configRegions_exclude = config.get('ec2', 'regions_exclude')
   205          if (configRegions == 'all'):
   206              if self.eucalyptus_host:
   207                  self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name)
   208              else:
   209                  for regionInfo in ec2.regions():
   210                      if regionInfo.name not in configRegions_exclude:
   211                          self.regions.append(regionInfo.name)
   212          else:
   213              self.regions = configRegions.split(",")
   214  
   215          # Destination addresses
   216          self.destination_variable = config.get('ec2', 'destination_variable')
   217          self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable')
   218  
   219          # Route53
   220          self.route53_enabled = config.getboolean('ec2', 'route53')
   221          self.route53_excluded_zones = []
   222          if config.has_option('ec2', 'route53_excluded_zones'):
   223              self.route53_excluded_zones.extend(
   224                  config.get('ec2', 'route53_excluded_zones', '').split(','))
   225  
   226          # Include RDS instances?
   227          self.rds_enabled = True
   228          if config.has_option('ec2', 'rds'):
   229              self.rds_enabled = config.getboolean('ec2', 'rds')
   230  
   231          # Return all EC2 and RDS instances (if RDS is enabled)
   232          if config.has_option('ec2', 'all_instances'):
   233              self.all_instances = config.getboolean('ec2', 'all_instances')
   234          else:
   235              self.all_instances = False
   236          if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled:
   237              self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances')
   238          else:
   239              self.all_rds_instances = False
   240  
   241          # Cache related
   242          cache_dir = os.path.expanduser(config.get('ec2', 'cache_path'))
   243          if not os.path.exists(cache_dir):
   244              os.makedirs(cache_dir)
   245  
   246          self.cache_path_cache = cache_dir + "/ansible-ec2.cache"
   247          self.cache_path_index = cache_dir + "/ansible-ec2.index"
   248          self.cache_max_age = config.getint('ec2', 'cache_max_age')
   249  
   250          # Configure nested groups instead of flat namespace.
   251          if config.has_option('ec2', 'nested_groups'):
   252              self.nested_groups = config.getboolean('ec2', 'nested_groups')
   253          else:
   254              self.nested_groups = False
   255  
   256          # Do we need to just include hosts that match a pattern?
   257          try:
   258              pattern_include = config.get('ec2', 'pattern_include')
   259              if pattern_include and len(pattern_include) > 0:
   260                  self.pattern_include = re.compile(pattern_include)
   261              else:
   262                  self.pattern_include = None
   263          except ConfigParser.NoOptionError, e:
   264              self.pattern_include = None
   265  
   266          # Do we need to exclude hosts that match a pattern?
   267          try:
   268              pattern_exclude = config.get('ec2', 'pattern_exclude');
   269              if pattern_exclude and len(pattern_exclude) > 0:
   270                  self.pattern_exclude = re.compile(pattern_exclude)
   271              else:
   272                  self.pattern_exclude = None
   273          except ConfigParser.NoOptionError, e:
   274              self.pattern_exclude = None
   275  
   276          # Instance filters (see boto and EC2 API docs)
   277          self.ec2_instance_filters = defaultdict(list)
   278          if config.has_option('ec2', 'instance_filters'):
   279              for x in config.get('ec2', 'instance_filters', '').split(','):
   280                  filter_key, filter_value = x.split('=')
   281                  self.ec2_instance_filters[filter_key].append(filter_value)
   282  
   283      def parse_cli_args(self):
   284          ''' Command line argument processing '''
   285  
   286          parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2')
   287          parser.add_argument('--list', action='store_true', default=True,
   288                             help='List instances (default: True)')
   289          parser.add_argument('--host', action='store',
   290                             help='Get all the variables about a specific instance')
   291          parser.add_argument('--refresh-cache', action='store_true', default=False,
   292                             help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)')
   293          self.args = parser.parse_args()
   294  
   295  
   296      def do_api_calls_update_cache(self):
   297          ''' Do API calls to each region, and save data in cache files '''
   298  
   299          if self.route53_enabled:
   300              self.get_route53_records()
   301  
   302          for region in self.regions:
   303              self.get_instances_by_region(region)
   304              if self.rds_enabled:
   305                  self.get_rds_instances_by_region(region)
   306  
   307          self.write_to_cache(self.inventory, self.cache_path_cache)
   308          self.write_to_cache(self.index, self.cache_path_index)
   309  
   310  
   311      def get_instances_by_region(self, region):
   312          ''' Makes an AWS EC2 API call to the list of instances in a particular
   313          region '''
   314  
   315          try:
   316              if self.eucalyptus:
   317                  conn = boto.connect_euca(host=self.eucalyptus_host)
   318                  conn.APIVersion = '2010-08-31'
   319              else:
   320                  conn = ec2.connect_to_region(region)
   321  
   322              # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported
   323              if conn is None:
   324                  print("region name: %s likely not supported, or AWS is down.  connection to region failed." % region)
   325                  sys.exit(1)
   326  
   327              reservations = []
   328              if self.ec2_instance_filters:
   329                  for filter_key, filter_values in self.ec2_instance_filters.iteritems():
   330                      reservations.extend(conn.get_all_instances(filters = { filter_key : filter_values }))
   331              else:
   332                  reservations = conn.get_all_instances()
   333  
   334              for reservation in reservations:
   335                  for instance in reservation.instances:
   336                      self.add_instance(instance, region)
   337  
   338          except boto.exception.BotoServerError, e:
   339              if  not self.eucalyptus:
   340                  print "Looks like AWS is down again:"
   341              print e
   342              sys.exit(1)
   343  
   344      def get_rds_instances_by_region(self, region):
   345          ''' Makes an AWS API call to the list of RDS instances in a particular
   346          region '''
   347  
   348          try:
   349              conn = rds.connect_to_region(region)
   350              if conn:
   351                  instances = conn.get_all_dbinstances()
   352                  for instance in instances:
   353                      self.add_rds_instance(instance, region)
   354          except boto.exception.BotoServerError, e:
   355              if not e.reason == "Forbidden":
   356                  print "Looks like AWS RDS is down: "
   357                  print e
   358                  sys.exit(1)
   359  
   360      def get_instance(self, region, instance_id):
   361          ''' Gets details about a specific instance '''
   362          if self.eucalyptus:
   363              conn = boto.connect_euca(self.eucalyptus_host)
   364              conn.APIVersion = '2010-08-31'
   365          else:
   366              conn = ec2.connect_to_region(region)
   367  
   368          # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported
   369          if conn is None:
   370              print("region name: %s likely not supported, or AWS is down.  connection to region failed." % region)
   371              sys.exit(1)
   372  
   373          reservations = conn.get_all_instances([instance_id])
   374          for reservation in reservations:
   375              for instance in reservation.instances:
   376                  return instance
   377  
   378      def add_instance(self, instance, region):
   379          ''' Adds an instance to the inventory and index, as long as it is
   380          addressable '''
   381  
   382          # Only want running instances unless all_instances is True
   383          if not self.all_instances and instance.state != 'running':
   384              return
   385  
   386          # Select the best destination address
   387          if instance.subnet_id:
   388              dest = getattr(instance, self.vpc_destination_variable)
   389          else:
   390              dest =  getattr(instance, self.destination_variable)
   391  
   392          if not dest:
   393              # Skip instances we cannot address (e.g. private VPC subnet)
   394              return
   395  
   396          # if we only want to include hosts that match a pattern, skip those that don't
   397          if self.pattern_include and not self.pattern_include.match(dest):
   398              return
   399  
   400          # if we need to exclude hosts that match a pattern, skip those
   401          if self.pattern_exclude and self.pattern_exclude.match(dest):
   402              return
   403  
   404          # Add to index
   405          self.index[dest] = [region, instance.id]
   406  
   407          # Inventory: Group by instance ID (always a group of 1)
   408          self.inventory[instance.id] = [dest]
   409          if self.nested_groups:
   410              self.push_group(self.inventory, 'instances', instance.id)
   411  
   412          # Inventory: Group by region
   413          if self.nested_groups:
   414              self.push_group(self.inventory, 'regions', region)
   415          else:
   416              self.push(self.inventory, region, dest)
   417  
   418          # Inventory: Group by availability zone
   419          self.push(self.inventory, instance.placement, dest)
   420          if self.nested_groups:
   421              self.push_group(self.inventory, region, instance.placement)
   422  
   423          # Inventory: Group by instance type
   424          type_name = self.to_safe('type_' + instance.instance_type)
   425          self.push(self.inventory, type_name, dest)
   426          if self.nested_groups:
   427              self.push_group(self.inventory, 'types', type_name)
   428  
   429          # Inventory: Group by key pair
   430          if instance.key_name:
   431              key_name = self.to_safe('key_' + instance.key_name)
   432              self.push(self.inventory, key_name, dest)
   433              if self.nested_groups:
   434                  self.push_group(self.inventory, 'keys', key_name)
   435  
   436          # Inventory: Group by VPC
   437          if instance.vpc_id:
   438              self.push(self.inventory, self.to_safe('vpc_id_' + instance.vpc_id), dest)
   439  
   440          # Inventory: Group by security group
   441          try:
   442              for group in instance.groups:
   443                  key = self.to_safe("security_group_" + group.name)
   444                  self.push(self.inventory, key, dest)
   445                  if self.nested_groups:
   446                      self.push_group(self.inventory, 'security_groups', key)
   447          except AttributeError:
   448              print 'Package boto seems a bit older.'
   449              print 'Please upgrade boto >= 2.3.0.'
   450              sys.exit(1)
   451  
   452          # Inventory: Group by tag keys
   453          for k, v in instance.tags.iteritems():
   454              key = self.to_safe("tag_" + k + "=" + v)
   455              self.push(self.inventory, key, dest)
   456              if self.nested_groups:
   457                  self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k))
   458                  self.push_group(self.inventory, self.to_safe("tag_" + k), key)
   459  
   460          # Inventory: Group by Route53 domain names if enabled
   461          if self.route53_enabled:
   462              route53_names = self.get_instance_route53_names(instance)
   463              for name in route53_names:
   464                  self.push(self.inventory, name, dest)
   465                  if self.nested_groups:
   466                      self.push_group(self.inventory, 'route53', name)
   467  
   468          # Global Tag: instances without tags
   469          if len(instance.tags) == 0:
   470              self.push(self.inventory, 'tag_none', dest)
   471              
   472          # Global Tag: tag all EC2 instances
   473          self.push(self.inventory, 'ec2', dest)
   474  
   475          self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
   476  
   477  
   478      def add_rds_instance(self, instance, region):
   479          ''' Adds an RDS instance to the inventory and index, as long as it is
   480          addressable '''
   481  
   482          # Only want available instances unless all_rds_instances is True
   483          if not self.all_rds_instances and instance.status != 'available':
   484              return
   485  
   486          # Select the best destination address
   487          #if instance.subnet_id:
   488              #dest = getattr(instance, self.vpc_destination_variable)
   489          #else:
   490              #dest =  getattr(instance, self.destination_variable)
   491          dest = instance.endpoint[0]
   492  
   493          if not dest:
   494              # Skip instances we cannot address (e.g. private VPC subnet)
   495              return
   496  
   497          # Add to index
   498          self.index[dest] = [region, instance.id]
   499  
   500          # Inventory: Group by instance ID (always a group of 1)
   501          self.inventory[instance.id] = [dest]
   502          if self.nested_groups:
   503              self.push_group(self.inventory, 'instances', instance.id)
   504  
   505          # Inventory: Group by region
   506          if self.nested_groups:
   507              self.push_group(self.inventory, 'regions', region)
   508          else:
   509              self.push(self.inventory, region, dest)
   510  
   511          # Inventory: Group by availability zone
   512          self.push(self.inventory, instance.availability_zone, dest)
   513          if self.nested_groups:
   514              self.push_group(self.inventory, region, instance.availability_zone)
   515  
   516          # Inventory: Group by instance type
   517          type_name = self.to_safe('type_' + instance.instance_class)
   518          self.push(self.inventory, type_name, dest)
   519          if self.nested_groups:
   520              self.push_group(self.inventory, 'types', type_name)
   521  
   522          # Inventory: Group by security group
   523          try:
   524              if instance.security_group:
   525                  key = self.to_safe("security_group_" + instance.security_group.name)
   526                  self.push(self.inventory, key, dest)
   527                  if self.nested_groups:
   528                      self.push_group(self.inventory, 'security_groups', key)
   529  
   530          except AttributeError:
   531              print 'Package boto seems a bit older.'
   532              print 'Please upgrade boto >= 2.3.0.'
   533              sys.exit(1)
   534  
   535          # Inventory: Group by engine
   536          self.push(self.inventory, self.to_safe("rds_" + instance.engine), dest)
   537          if self.nested_groups:
   538              self.push_group(self.inventory, 'rds_engines', self.to_safe("rds_" + instance.engine))
   539  
   540          # Inventory: Group by parameter group
   541          self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), dest)
   542          if self.nested_groups:
   543              self.push_group(self.inventory, 'rds_parameter_groups', self.to_safe("rds_parameter_group_" + instance.parameter_group.name))
   544  
   545          # Global Tag: all RDS instances
   546          self.push(self.inventory, 'rds', dest)
   547  
   548          self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
   549  
   550  
   551      def get_route53_records(self):
   552          ''' Get and store the map of resource records to domain names that
   553          point to them. '''
   554  
   555          r53_conn = route53.Route53Connection()
   556          all_zones = r53_conn.get_zones()
   557  
   558          route53_zones = [ zone for zone in all_zones if zone.name[:-1]
   559                            not in self.route53_excluded_zones ]
   560  
   561          self.route53_records = {}
   562  
   563          for zone in route53_zones:
   564              rrsets = r53_conn.get_all_rrsets(zone.id)
   565  
   566              for record_set in rrsets:
   567                  record_name = record_set.name
   568  
   569                  if record_name.endswith('.'):
   570                      record_name = record_name[:-1]
   571  
   572                  for resource in record_set.resource_records:
   573                      self.route53_records.setdefault(resource, set())
   574                      self.route53_records[resource].add(record_name)
   575  
   576  
   577      def get_instance_route53_names(self, instance):
   578          ''' Check if an instance is referenced in the records we have from
   579          Route53. If it is, return the list of domain names pointing to said
   580          instance. If nothing points to it, return an empty list. '''
   581  
   582          instance_attributes = [ 'public_dns_name', 'private_dns_name',
   583                                  'ip_address', 'private_ip_address' ]
   584  
   585          name_list = set()
   586  
   587          for attrib in instance_attributes:
   588              try:
   589                  value = getattr(instance, attrib)
   590              except AttributeError:
   591                  continue
   592  
   593              if value in self.route53_records:
   594                  name_list.update(self.route53_records[value])
   595  
   596          return list(name_list)
   597  
   598  
   599      def get_host_info_dict_from_instance(self, instance):
   600          instance_vars = {}
   601          for key in vars(instance):
   602              value = getattr(instance, key)
   603              key = self.to_safe('ec2_' + key)
   604  
   605              # Handle complex types
   606              # state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518
   607              if key == 'ec2__state':
   608                  instance_vars['ec2_state'] = instance.state or ''
   609                  instance_vars['ec2_state_code'] = instance.state_code
   610              elif key == 'ec2__previous_state':
   611                  instance_vars['ec2_previous_state'] = instance.previous_state or ''
   612                  instance_vars['ec2_previous_state_code'] = instance.previous_state_code
   613              elif type(value) in [int, bool]:
   614                  instance_vars[key] = value
   615              elif type(value) in [str, unicode]:
   616                  instance_vars[key] = value.strip()
   617              elif type(value) == type(None):
   618                  instance_vars[key] = ''
   619              elif key == 'ec2_region':
   620                  instance_vars[key] = value.name
   621              elif key == 'ec2__placement':
   622                  instance_vars['ec2_placement'] = value.zone
   623              elif key == 'ec2_tags':
   624                  for k, v in value.iteritems():
   625                      key = self.to_safe('ec2_tag_' + k)
   626                      instance_vars[key] = v
   627              elif key == 'ec2_groups':
   628                  group_ids = []
   629                  group_names = []
   630                  for group in value:
   631                      group_ids.append(group.id)
   632                      group_names.append(group.name)
   633                  instance_vars["ec2_security_group_ids"] = ','.join([str(i) for i in group_ids])
   634                  instance_vars["ec2_security_group_names"] = ','.join([str(i) for i in group_names])
   635              else:
   636                  pass
   637                  # TODO Product codes if someone finds them useful
   638                  #print key
   639                  #print type(value)
   640                  #print value
   641  
   642          return instance_vars
   643  
   644      def get_host_info(self):
   645          ''' Get variables about a specific host '''
   646  
   647          if len(self.index) == 0:
   648              # Need to load index from cache
   649              self.load_index_from_cache()
   650  
   651          if not self.args.host in self.index:
   652              # try updating the cache
   653              self.do_api_calls_update_cache()
   654              if not self.args.host in self.index:
   655                  # host might not exist anymore
   656                  return self.json_format_dict({}, True)
   657  
   658          (region, instance_id) = self.index[self.args.host]
   659  
   660          instance = self.get_instance(region, instance_id)
   661          return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True)
   662  
   663      def push(self, my_dict, key, element):
   664          ''' Push an element onto an array that may not have been defined in
   665          the dict '''
   666          group_info = my_dict.setdefault(key, [])
   667          if isinstance(group_info, dict):
   668              host_list = group_info.setdefault('hosts', [])
   669              host_list.append(element)
   670          else:
   671              group_info.append(element)
   672  
   673      def push_group(self, my_dict, key, element):
   674          ''' Push a group as a child of another group. '''
   675          parent_group = my_dict.setdefault(key, {})
   676          if not isinstance(parent_group, dict):
   677              parent_group = my_dict[key] = {'hosts': parent_group}
   678          child_groups = parent_group.setdefault('children', [])
   679          if element not in child_groups:
   680              child_groups.append(element)
   681  
   682      def get_inventory_from_cache(self):
   683          ''' Reads the inventory from the cache file and returns it as a JSON
   684          object '''
   685  
   686          cache = open(self.cache_path_cache, 'r')
   687          json_inventory = cache.read()
   688          return json_inventory
   689  
   690  
   691      def load_index_from_cache(self):
   692          ''' Reads the index from the cache file sets self.index '''
   693  
   694          cache = open(self.cache_path_index, 'r')
   695          json_index = cache.read()
   696          self.index = json.loads(json_index)
   697  
   698  
   699      def write_to_cache(self, data, filename):
   700          ''' Writes data in JSON format to a file '''
   701  
   702          json_data = self.json_format_dict(data, True)
   703          cache = open(filename, 'w')
   704          cache.write(json_data)
   705          cache.close()
   706  
   707  
   708      def to_safe(self, word):
   709          ''' Converts 'bad' characters in a string to underscores so they can be
   710          used as Ansible groups '''
   711  
   712          return re.sub("[^A-Za-z0-9\-]", "_", word)
   713  
   714  
   715      def json_format_dict(self, data, pretty=False):
   716          ''' Converts a dict to a JSON object and dumps it as a formatted
   717          string '''
   718  
   719          if pretty:
   720              return json.dumps(data, sort_keys=True, indent=2)
   721          else:
   722              return json.dumps(data)
   723  
   724  
   725  # Run the script
   726  Ec2Inventory()
   727