From 382c6fe05b14b42465b79709e03574ce13f3e46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 22:07:39 +0200 Subject: [PATCH 01/26] Adds basic configuration to ec2.ini to support ElastiCache Clusters and Nodes --- plugins/inventory/ec2.ini | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/inventory/ec2.ini b/plugins/inventory/ec2.ini index 6583160f0f..a835b01fe7 100644 --- a/plugins/inventory/ec2.ini +++ b/plugins/inventory/ec2.ini @@ -47,6 +47,9 @@ route53 = False # To exclude RDS instances from the inventory, uncomment and set to False. #rds = False +# To exclude ElastiCache instances from the inventory, uncomment and set to False. +#elasticache = False + # Additionally, you can specify the list of zones to exclude looking up in # 'route53_excluded_zones' as a comma-separated list. # route53_excluded_zones = samplezone1.com, samplezone2.com @@ -59,6 +62,12 @@ all_instances = False # 'all_rds_instances' to True return all RDS instances regardless of state. all_rds_instances = False +# By default, only ElastiCache clusters and nodes in the 'available' state +# are returned. Set 'all_elasticache_clusters' and/or 'all_elastic_nodes' +# to True return all ElastiCache clusters and nodes, regardless of state. +all_elasticache_clusters = False +all_elasticache_nodes = False + # API calls to EC2 are slow. For this reason, we cache the results of an API # call. Set this to the path you want cache files to be written to. Two files # will be written to this directory: @@ -89,6 +98,9 @@ group_by_tag_none = True group_by_route53_names = True group_by_rds_engine = True group_by_rds_parameter_group = True +group_by_elasticache_engine = True +group_by_elasticache_cluster = True +group_by_elasticache_parameter_group = True # If you only want to include hosts that match a certain regular expression # pattern_include = stage-* From bc80bd36afbf71b7feab71edc5dfcc5004a0e1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 22:12:03 +0200 Subject: [PATCH 02/26] Adds the necessary logic to ec2.py to load ElastiCache related configuration --- plugins/inventory/ec2.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 16ac93f5ee..c7fa6bdb15 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -121,6 +121,7 @@ from time import time import boto from boto import ec2 from boto import rds +from boto import elasticache from boto import route53 import six @@ -232,6 +233,11 @@ class Ec2Inventory(object): if config.has_option('ec2', 'rds'): self.rds_enabled = config.getboolean('ec2', 'rds') + # Include ElastiCache instances? + self.elasticache_enabled = True + if config.has_option('ec2', 'elasticache'): + self.elasticache_enabled = config.getboolean('ec2', 'elasticache') + # Return all EC2 and RDS instances (if RDS is enabled) if config.has_option('ec2', 'all_instances'): self.all_instances = config.getboolean('ec2', 'all_instances') @@ -242,6 +248,18 @@ class Ec2Inventory(object): else: self.all_rds_instances = False + # Return all ElastiCache clusters? (if ElastiCache is enabled) + if config.has_option('ec2', 'all_elasticache_clusters') and self.elasticache_enabled: + self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters') + else: + self.all_elasticache_clusters = False + + # Return all ElastiCache nodes? (if ElastiCache is enabled) + if config.has_option('ec2', 'all_elasticache_nodes') and self.elasticache_enabled: + self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes') + else: + self.all_elasticache_nodes = False + # Cache related cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) if not os.path.exists(cache_dir): @@ -272,6 +290,9 @@ class Ec2Inventory(object): 'group_by_route53_names', 'group_by_rds_engine', 'group_by_rds_parameter_group', + 'group_by_elasticache_engine', + 'group_by_elasticache_cluster', + 'group_by_elasticache_parameter_group', ] for option in group_by_options: if config.has_option('ec2', option): From 50b320615eee3235b5178637ad8793cefe79c7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 22:13:27 +0200 Subject: [PATCH 03/26] Little improvement in the organization of the configuration loader method --- plugins/inventory/ec2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index c7fa6bdb15..80afee7444 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -238,11 +238,13 @@ class Ec2Inventory(object): if config.has_option('ec2', 'elasticache'): self.elasticache_enabled = config.getboolean('ec2', 'elasticache') - # Return all EC2 and RDS instances (if RDS is enabled) + # Return all EC2 instances? if config.has_option('ec2', 'all_instances'): self.all_instances = config.getboolean('ec2', 'all_instances') else: self.all_instances = False + + # Return all RDS instances? (if RDS is enabled) if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled: self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances') else: From 06c6db8e6bfc8d3484720aea8cb902fd971f853c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 22:21:40 +0200 Subject: [PATCH 04/26] Adds get_elasticache_clusters_by_region method to perform the API call to AWS (and sadly finds out that Boto support for ElastiCache is very outdated...) --- plugins/inventory/ec2.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 80afee7444..f64f4a9315 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -357,6 +357,8 @@ class Ec2Inventory(object): self.get_instances_by_region(region) if self.rds_enabled: self.get_rds_instances_by_region(region) + if self.elasticache_enabled: + self.get_elasticache_clusters_by_region(region) self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.index, self.cache_path_index) @@ -417,6 +419,40 @@ class Ec2Inventory(object): error = "Looks like AWS RDS is down:\n%s" % e.message self.fail_with_error(error) + def get_elasticache_clusters_by_region(self, region): + ''' Makes an AWS API call to the list of ElastiCache clusters in a + particular region.''' + + # ElastiCache boto module doesn't provide a get_all_intances method, + # that's why we need to call describe directly (it would be called by + # the shorthand method anyway...) + try: + conn = elasticache.connect_to_region(region) + if conn: + response = conn.describe_cache_clusters() + + except boto.exception.BotoServerError as e: + error = e.reason + + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() + if not e.reason == "Forbidden": + error = "Looks like AWS RDS is down:\n%s" % e.message + self.fail_with_error(error) + + try: + # Boto also doesn't provide wrapper classes to CacheClusters or + # CacheNodes. Because of that wo can't make use of the get_list + # method in the AWSQueryConnection. Let's do the work manually + clusters = response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['CacheClusters'] + + except KeyError as e: + error = "ElastiCache query to AWS failed (unexpected format)." + self.fail_with_error(error) + + for cluster in clusters: + self.add_elasticache_cluster(cluster, region) + def get_auth_error_message(self): ''' create an informative error message if there is an issue authenticating''' errors = ["Authentication error retrieving ec2 inventory."] From 2cd76cf0e3d160e1e8a7b31a35772ab71bdc75ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 22:41:05 +0200 Subject: [PATCH 05/26] Creates add_elasticache_cluster method to digest the API answer about ElastiCache clusters --- plugins/inventory/ec2.py | 88 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index f64f4a9315..0f61413451 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -688,6 +688,94 @@ class Ec2Inventory(object): self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance) + def add_elasticache_cluster(self, cluster, region): + ''' Adds an ElastiCache cluster to the inventory and index, as long as + it's nodes are addressable ''' + + # Only want available clusters unless all_elasticache_clusters is True + if not self.all_elasticache_clusters and cluster['CacheClusterStatus'] != 'available': + return + + # Select the best destination address + if 'ConfigurationEndpoint' in cluster and cluster['ConfigurationEndpoint']: + # Memcached cluster + dest = cluster['ConfigurationEndpoint']['Address'] + else: + # Redis sigle node cluster + dest = cluster['CacheNodes'][0]['Endpoint']['Address'] + + if not dest: + # Skip clusters we cannot address (e.g. private VPC subnet) + return + + # Add to index + self.index[dest] = [region, cluster['CacheClusterId']] + + # Inventory: Group by instance ID (always a group of 1) + if self.group_by_instance_id: + self.inventory[cluster['CacheClusterId']] = [dest] + if self.nested_groups: + self.push_group(self.inventory, 'instances', cluster['CacheClusterId']) + + # Inventory: Group by region + if self.group_by_region: + self.push(self.inventory, region, dest) + if self.nested_groups: + self.push_group(self.inventory, 'regions', region) + + # Inventory: Group by availability zone + if self.group_by_availability_zone: + self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) + if self.nested_groups: + if self.group_by_region: + self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) + self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) + + # Inventory: Group by node type + if self.group_by_instance_type: + type_name = self.to_safe('type_' + cluster['CacheNodeType']) + self.push(self.inventory, type_name, dest) + if self.nested_groups: + self.push_group(self.inventory, 'types', type_name) + + # Inventory: Group by VPC + # if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id: + # vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id) + # self.push(self.inventory, vpc_id_name, dest) + # if self.nested_groups: + # self.push_group(self.inventory, 'vpcs', vpc_id_name) + + # Inventory: Group by security group + if self.group_by_security_group: + if 'SecurityGroups' in cluster: + for security_group in cluster['SecurityGroups']: + key = self.to_safe("security_group_" + security_group['SecurityGroupId']) + self.push(self.inventory, key, dest) + if self.nested_groups: + self.push_group(self.inventory, 'security_groups', key) + + # Inventory: Group by engine + if self.group_by_elasticache_engine: + self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_engines', self.to_safe(cluster['Engine'])) + + # Inventory: Group by parameter group + if self.group_by_elasticache_parameter_group: + self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe(cluster['CacheParameterGroup']['CacheParameterGroupName'])) + + # Inventory: Group by replication group + if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']: + self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe(cluster['ReplicationGroupId'])) + + # Global Tag: all ElastiCache clusters + self.push(self.inventory, 'elasticache_clusters', cluster['CacheClusterId']) + + self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(cluster) def get_route53_records(self): ''' Get and store the map of resource records to domain names that From c6f2b08a6010d2309f25c3d82bd97dd3794562f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 22:57:03 +0200 Subject: [PATCH 06/26] Creates get_host_info_dict_from_describe_dict helper method to translate information from a 'describe' call (we don't have instance objects in this case) --- plugins/inventory/ec2.py | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 0f61413451..b2374cc26f 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -775,7 +775,9 @@ class Ec2Inventory(object): # Global Tag: all ElastiCache clusters self.push(self.inventory, 'elasticache_clusters', cluster['CacheClusterId']) - self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(cluster) + host_info = self.get_host_info_dict_from_describe_dict(cluster) + + self.inventory["_meta"]["hostvars"][dest] = host_info def get_route53_records(self): ''' Get and store the map of resource records to domain names that @@ -870,6 +872,43 @@ class Ec2Inventory(object): return instance_vars + def get_host_info_dict_from_describe_dict(self, describe_dict): + ''' Parses the dictionary returned by the API call into a flat list + of parameters. This method should be used only when 'describe' is + used directly because Boto doesn't provide specific classes. ''' + + host_info = {} + for key in describe_dict: + value = describe_dict[key] + key = self.to_safe('ec2_' + key) + + # Handle complex types + if key == 'ec2_ConfigurationEndpoint' and value: + host_info['ec2_configuration_endpoint_address'] = value['Address'] + host_info['ec2_configuration_endpoint_port'] = value['Port'] + if key == 'ec2_Endpoint' and value: + host_info['ec2_endpoint_address'] = value['Address'] + host_info['ec2_endpoint_port'] = value['Port'] + elif key == 'ec2_CacheParameterGroup': + host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] + host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] + elif key == 'ec2_SecurityGroups': + sg_ids = [] + for sg in value: + sg_ids.append(sg['SecurityGroupId']) + host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids]) + elif type(value) in [int, bool]: + host_info[key] = value + elif isinstance(value, six.string_types): + host_info[key] = value.strip() + elif type(value) == type(None): + host_info[key] = '' + + else: + pass + + return host_info + def get_host_info(self): ''' Get variables about a specific host ''' From dbb0304ceab81d1364e9fa9609cf994925abf745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:01:13 +0200 Subject: [PATCH 07/26] Adds uncammelize helper method to put the labels in the expected output format --- plugins/inventory/ec2.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index b2374cc26f..0352a5e4f4 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -880,19 +880,19 @@ class Ec2Inventory(object): host_info = {} for key in describe_dict: value = describe_dict[key] - key = self.to_safe('ec2_' + key) + key = self.to_safe('ec2_' + self.uncammelize(key)) # Handle complex types - if key == 'ec2_ConfigurationEndpoint' and value: + if key == 'ec2_configuration_endpoint' and value: host_info['ec2_configuration_endpoint_address'] = value['Address'] host_info['ec2_configuration_endpoint_port'] = value['Port'] - if key == 'ec2_Endpoint' and value: + if key == 'ec2_endpoint' and value: host_info['ec2_endpoint_address'] = value['Address'] host_info['ec2_endpoint_port'] = value['Port'] - elif key == 'ec2_CacheParameterGroup': + elif key == 'ec2_cache_parameter_group': host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] - elif key == 'ec2_SecurityGroups': + elif key == 'ec2_security_groups': sg_ids = [] for sg in value: sg_ids.append(sg['SecurityGroupId']) @@ -972,6 +972,9 @@ class Ec2Inventory(object): cache.write(json_data) cache.close() + def uncammelize(self, key): + temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp).lower() def to_safe(self, word): ''' Converts 'bad' characters in a string to underscores so they can be From 98a5531966ec4693ddb3f72f50498b7bd611434e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:03:15 +0200 Subject: [PATCH 08/26] Makes the API requests to return nodes' information too --- plugins/inventory/ec2.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 0352a5e4f4..165e97099d 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -420,8 +420,8 @@ class Ec2Inventory(object): self.fail_with_error(error) def get_elasticache_clusters_by_region(self, region): - ''' Makes an AWS API call to the list of ElastiCache clusters in a - particular region.''' + ''' Makes an AWS API call to the list of ElastiCache clusters (with + nodes' info) in a particular region.''' # ElastiCache boto module doesn't provide a get_all_intances method, # that's why we need to call describe directly (it would be called by @@ -429,7 +429,9 @@ class Ec2Inventory(object): try: conn = elasticache.connect_to_region(region) if conn: - response = conn.describe_cache_clusters() + # show_cache_node_info = True + # because we also want nodes' information + response = conn.describe_cache_clusters(None, None, None, True) except boto.exception.BotoServerError as e: error = e.reason From 2a242a0e1bb72dcbb226a5ef073103a5008f1c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:08:10 +0200 Subject: [PATCH 09/26] Creates add_elasticache_node method in ec2.py --- plugins/inventory/ec2.py | 99 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 165e97099d..cec994798c 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -781,6 +781,105 @@ class Ec2Inventory(object): self.inventory["_meta"]["hostvars"][dest] = host_info + # Add the nodes + for node in cluster['CacheNodes']: + self.add_elasticache_node(node, cluster, region) + + def add_elasticache_node(self, node, cluster, region): + ''' Adds an ElastiCache node to the inventory and index, as long as + it is addressable ''' + + # Only want available nodes unless all_elasticache_nodes is True + if not self.all_elasticache_nodes and node['CacheNodeStatus'] != 'available': + return + + # Select the best destination address + dest = node['Endpoint']['Address'] + + if not dest: + # Skip nodes we cannot address (e.g. private VPC subnet) + return + + node_id = self.to_safe(cluster['CacheClusterId'] + '_' + node['CacheNodeId']) + + # Add to index + self.index[dest] = [region, node_id] + + # Inventory: Group by node ID (always a group of 1) + if self.group_by_instance_id: + self.inventory[node_id] = [dest] + if self.nested_groups: + self.push_group(self.inventory, 'instances', node_id) + + # Inventory: Group by region + if self.group_by_region: + self.push(self.inventory, region, dest) + if self.nested_groups: + self.push_group(self.inventory, 'regions', region) + + # Inventory: Group by availability zone + if self.group_by_availability_zone: + self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) + if self.nested_groups: + if self.group_by_region: + self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) + self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) + + # Inventory: Group by node type + if self.group_by_instance_type: + type_name = self.to_safe('type_' + cluster['CacheNodeType']) + self.push(self.inventory, type_name, dest) + if self.nested_groups: + self.push_group(self.inventory, 'types', type_name) + + # Inventory: Group by VPC + # if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id: + # vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id) + # self.push(self.inventory, vpc_id_name, dest) + # if self.nested_groups: + # self.push_group(self.inventory, 'vpcs', vpc_id_name) + + # Inventory: Group by security group + if self.group_by_security_group: + if 'SecurityGroups' in cluster: + for security_group in cluster['SecurityGroups']: + key = self.to_safe("security_group_" + security_group['SecurityGroupId']) + self.push(self.inventory, key, dest) + if self.nested_groups: + self.push_group(self.inventory, 'security_groups', key) + + # Inventory: Group by engine + if self.group_by_elasticache_engine: + self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_engines', self.to_safe("elasticache_" + cluster['Engine'])) + + # Inventory: Group by parameter group + # if self.group_by_elasticache_parameter_group: + # self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest) + # if self.nested_groups: + # self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName'])) + + # Inventory: Group by replication group + # if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']: + # self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest) + # if self.nested_groups: + # self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe("elasticache_" + cluster['ReplicationGroupId'])) + + # Inventory: Group by ElastiCache Cluster + if self.group_by_elasticache_cluster: + self.push(self.inventory, self.to_safe("elasticache_cluster_" + cluster['CacheClusterId']), dest) + + # Global Tag: all ElastiCache nodes + self.push(self.inventory, 'elasticache_nodes', dest) + + host_info = self.get_host_info_dict_from_describe_dict(node) + + if dest in self.inventory["_meta"]["hostvars"]: + self.inventory["_meta"]["hostvars"][dest].update(host_info) + else: + self.inventory["_meta"]["hostvars"][dest] = host_info + def get_route53_records(self): ''' Get and store the map of resource records to domain names that point to them. ''' From e64daba8e72deee8b97d06ed2a3076ed32a607ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:10:33 +0200 Subject: [PATCH 10/26] Adds a flag (is_redis) to prevent duplicity of information about Redis single node clusters --- plugins/inventory/ec2.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index cec994798c..3dddbc65b2 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -702,9 +702,13 @@ class Ec2Inventory(object): if 'ConfigurationEndpoint' in cluster and cluster['ConfigurationEndpoint']: # Memcached cluster dest = cluster['ConfigurationEndpoint']['Address'] + is_redis = False else: # Redis sigle node cluster + # Because all Redis clusters are single nodes, we'll merge the + # info from the cluster with info about the node dest = cluster['CacheNodes'][0]['Endpoint']['Address'] + is_redis = True if not dest: # Skip clusters we cannot address (e.g. private VPC subnet) @@ -720,13 +724,13 @@ class Ec2Inventory(object): self.push_group(self.inventory, 'instances', cluster['CacheClusterId']) # Inventory: Group by region - if self.group_by_region: + if self.group_by_region and not is_redis: self.push(self.inventory, region, dest) if self.nested_groups: self.push_group(self.inventory, 'regions', region) # Inventory: Group by availability zone - if self.group_by_availability_zone: + if self.group_by_availability_zone and not is_redis: self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) if self.nested_groups: if self.group_by_region: @@ -734,7 +738,7 @@ class Ec2Inventory(object): self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) # Inventory: Group by node type - if self.group_by_instance_type: + if self.group_by_instance_type and not is_redis: type_name = self.to_safe('type_' + cluster['CacheNodeType']) self.push(self.inventory, type_name, dest) if self.nested_groups: @@ -748,7 +752,7 @@ class Ec2Inventory(object): # self.push_group(self.inventory, 'vpcs', vpc_id_name) # Inventory: Group by security group - if self.group_by_security_group: + if self.group_by_security_group and not is_redis: if 'SecurityGroups' in cluster: for security_group in cluster['SecurityGroups']: key = self.to_safe("security_group_" + security_group['SecurityGroupId']) @@ -757,7 +761,7 @@ class Ec2Inventory(object): self.push_group(self.inventory, 'security_groups', key) # Inventory: Group by engine - if self.group_by_elasticache_engine: + if self.group_by_elasticache_engine and not is_redis: self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) if self.nested_groups: self.push_group(self.inventory, 'elasticache_engines', self.to_safe(cluster['Engine'])) From 22020ac3cdf7586273ec362771227f616185b07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:12:52 +0200 Subject: [PATCH 11/26] Adds the necessary config entries to ec2.ini, to support ElastiCache replication groups --- plugins/inventory/ec2.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/inventory/ec2.ini b/plugins/inventory/ec2.ini index a835b01fe7..b6818e876c 100644 --- a/plugins/inventory/ec2.ini +++ b/plugins/inventory/ec2.ini @@ -65,6 +65,7 @@ all_rds_instances = False # By default, only ElastiCache clusters and nodes in the 'available' state # are returned. Set 'all_elasticache_clusters' and/or 'all_elastic_nodes' # to True return all ElastiCache clusters and nodes, regardless of state. +all_elasticache_replication_groups = False all_elasticache_clusters = False all_elasticache_nodes = False @@ -101,6 +102,7 @@ group_by_rds_parameter_group = True group_by_elasticache_engine = True group_by_elasticache_cluster = True group_by_elasticache_parameter_group = True +group_by_elasticache_replication_group = True # If you only want to include hosts that match a certain regular expression # pattern_include = stage-* From 40ce0727470cf820999dc1591d76e964e57bbdd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:14:00 +0200 Subject: [PATCH 12/26] Adds the logic to process the new config entries about ElastiCache replication groups --- plugins/inventory/ec2.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 3dddbc65b2..5004a704d9 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -250,6 +250,12 @@ class Ec2Inventory(object): else: self.all_rds_instances = False + # Return all ElastiCache replication groups? (if ElastiCache is enabled) + if config.has_option('ec2', 'all_elasticache_replication_groups') and self.elasticache_enabled: + self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups') + else: + self.all_elasticache_replication_groups = False + # Return all ElastiCache clusters? (if ElastiCache is enabled) if config.has_option('ec2', 'all_elasticache_clusters') and self.elasticache_enabled: self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters') @@ -295,6 +301,7 @@ class Ec2Inventory(object): 'group_by_elasticache_engine', 'group_by_elasticache_cluster', 'group_by_elasticache_parameter_group', + 'group_by_elasticache_replication_group', ] for option in group_by_options: if config.has_option('ec2', option): From c18f6cae11960735e9be6db0984c35df002abf9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:15:33 +0200 Subject: [PATCH 13/26] Creates get_elasticache_replication_groups_by_region method to handle the API call --- plugins/inventory/ec2.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 5004a704d9..5f80c47675 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -366,6 +366,7 @@ class Ec2Inventory(object): self.get_rds_instances_by_region(region) if self.elasticache_enabled: self.get_elasticache_clusters_by_region(region) + self.get_elasticache_replication_groups_by_region(region) self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.index, self.cache_path_index) @@ -462,6 +463,40 @@ class Ec2Inventory(object): for cluster in clusters: self.add_elasticache_cluster(cluster, region) + def get_elasticache_replication_groups_by_region(self, region): + ''' Makes an AWS API call to the list of ElastiCache replication groups + in a particular region.''' + + # ElastiCache boto module doesn't provide a get_all_intances method, + # that's why we need to call describe directly (it would be called by + # the shorthand method anyway...) + try: + conn = elasticache.connect_to_region(region) + if conn: + response = conn.describe_replication_groups() + + except boto.exception.BotoServerError as e: + error = e.reason + + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() + if not e.reason == "Forbidden": + error = "Looks like AWS RDS is down:\n%s" % e.message + self.fail_with_error(error) + + try: + # Boto also doesn't provide wrapper classes to ReplicationGroups + # Because of that wo can't make use of the get_list method in the + # AWSQueryConnection. Let's do the work manually + replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups'] + + except KeyError as e: + error = "ElastiCache query to AWS failed (unexpected format)." + self.fail_with_error(error) + + for replication_group in replication_groups: + self.add_elasticache_replication_group(replication_group, region) + def get_auth_error_message(self): ''' create an informative error message if there is an issue authenticating''' errors = ["Authentication error retrieving ec2 inventory."] From 069ee116995bdab33302287fcf5bce9034c7d893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:18:21 +0200 Subject: [PATCH 14/26] Creates add_elasticache_replication_group method in ec2.py dynamic inventory script --- plugins/inventory/ec2.py | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 5f80c47675..078e07b97b 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -926,6 +926,58 @@ class Ec2Inventory(object): else: self.inventory["_meta"]["hostvars"][dest] = host_info + def add_elasticache_replication_group(self, replication_group, region): + ''' Adds an ElastiCache replication group to the inventory and index ''' + + # Only want available clusters unless all_elasticache_replication_groups is True + if not self.all_elasticache_replication_groups and replication_group['Status'] != 'available': + return + + # Select the best destination address (PrimaryEndpoint) + dest = replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address'] + + if not dest: + # Skip clusters we cannot address (e.g. private VPC subnet) + return + + # Add to index + self.index[dest] = [region, replication_group['ReplicationGroupId']] + + # Inventory: Group by ID (always a group of 1) + if self.group_by_instance_id: + self.inventory[replication_group['ReplicationGroupId']] = [dest] + if self.nested_groups: + self.push_group(self.inventory, 'instances', replication_group['ReplicationGroupId']) + + # Inventory: Group by region + if self.group_by_region: + self.push(self.inventory, region, dest) + if self.nested_groups: + self.push_group(self.inventory, 'regions', region) + + # Inventory: Group by availability zone (doesn't apply to replication groups) + + # Inventory: Group by node type (doesn't apply to replication groups) + + # Inventory: Group by VPC (information not available in the current + # AWS API version for replication groups + + # Inventory: Group by security group (doesn't apply to replication groups) + # Check this value in cluster level + + # Inventory: Group by engine (replication groups are always Redis) + if self.group_by_elasticache_engine: + self.push(self.inventory, 'elasticache_redis', dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_engines', 'redis') + + # Global Tag: all ElastiCache clusters + self.push(self.inventory, 'elasticache_replication_groups', replication_group['ReplicationGroupId']) + + host_info = self.get_host_info_dict_from_describe_dict(replication_group) + + self.inventory["_meta"]["hostvars"][dest] = host_info + def get_route53_records(self): ''' Get and store the map of resource records to domain names that point to them. ''' From f25ad9dc51db9d906174dd7c0e7c1a8905845952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:21:33 +0200 Subject: [PATCH 15/26] Adds the appropriate key checks for ElastiCache replication groups in get_dict_from_describe_dict method --- plugins/inventory/ec2.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 078e07b97b..9aec945472 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -1088,6 +1088,11 @@ class Ec2Inventory(object): if key == 'ec2_endpoint' and value: host_info['ec2_endpoint_address'] = value['Address'] host_info['ec2_endpoint_port'] = value['Port'] + if key == 'ec2_node_groups' and value: + host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address'] + host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port'] + if key == 'ec2_member_clusters' and value: + host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) elif key == 'ec2_cache_parameter_group': host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] From ffd74049da595a2d12b081a9b4c4e039a233da8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:24:51 +0200 Subject: [PATCH 16/26] Comments about the naming pattern in the script, that certainly deserves future refactoring --- plugins/inventory/ec2.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 9aec945472..4b205c0d95 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -1076,6 +1076,11 @@ class Ec2Inventory(object): of parameters. This method should be used only when 'describe' is used directly because Boto doesn't provide specific classes. ''' + # I really don't agree with prefixing everything with 'ec2' + # because EC2, RDS and ElastiCache are different services. + # I'm just following the pattern used until now to not break any + # compatibility. + host_info = {} for key in describe_dict: value = describe_dict[key] From 43f9a653d0c6edf0a6c69587ef76f094e7fa1e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:27:16 +0200 Subject: [PATCH 17/26] Process CacheNodeIdsToReboot complex type for cache clusters --- plugins/inventory/ec2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 4b205c0d95..4bdde428ce 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -1099,6 +1099,7 @@ class Ec2Inventory(object): if key == 'ec2_member_clusters' and value: host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) elif key == 'ec2_cache_parameter_group': + host_info["ec2_cache_node_ids_to_reboot"] = ','.join([str(i) for i in value['CacheNodeIdsToReboot']]) host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] elif key == 'ec2_security_groups': From e692a18a2990505b37aede4c6e814141ec110e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:29:05 +0200 Subject: [PATCH 18/26] Process information about primary clusters for ElastiCache replication groups --- plugins/inventory/ec2.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 4bdde428ce..dddcf587af 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -1096,6 +1096,11 @@ class Ec2Inventory(object): if key == 'ec2_node_groups' and value: host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address'] host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port'] + for node in value[0]['NodeGroupMembers']: + if node['CurrentRole'] == 'primary': + host_info['ec2_primary_cluster_address'] = node['ReadEndpoint']['Address'] + host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port'] + host_info['ec2_primary_cluster_id'] = node['CacheClusterId'] if key == 'ec2_member_clusters' and value: host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) elif key == 'ec2_cache_parameter_group': From 41b034a5d2d2178e93ae5667a65028ad48307367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:29:55 +0200 Subject: [PATCH 19/26] Process information about replica clusters for ElastiCache replication groups --- plugins/inventory/ec2.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index dddcf587af..76fc83497d 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -1096,11 +1096,17 @@ class Ec2Inventory(object): if key == 'ec2_node_groups' and value: host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address'] host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port'] + replica_count = 0 for node in value[0]['NodeGroupMembers']: if node['CurrentRole'] == 'primary': host_info['ec2_primary_cluster_address'] = node['ReadEndpoint']['Address'] host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port'] host_info['ec2_primary_cluster_id'] = node['CacheClusterId'] + elif node['CurrentRole'] == 'replica': + host_info['ec2_replica_cluster_address_'+ str(replica_count)] = node['ReadEndpoint']['Address'] + host_info['ec2_replica_cluster_port_'+ str(replica_count)] = node['ReadEndpoint']['Port'] + host_info['ec2_replica_cluster_id_'+ str(replica_count)] = node['CacheClusterId'] + replica_count += 1 if key == 'ec2_member_clusters' and value: host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) elif key == 'ec2_cache_parameter_group': From 77a2ad0e8cc5b6d09a39d21a926060df1976edb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:32:10 +0200 Subject: [PATCH 20/26] Improves code organization in get_dict_from_describe_dict method --- plugins/inventory/ec2.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 76fc83497d..9cb7219f66 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -1087,12 +1087,18 @@ class Ec2Inventory(object): key = self.to_safe('ec2_' + self.uncammelize(key)) # Handle complex types + + # Target: Memcached Cache Clusters if key == 'ec2_configuration_endpoint' and value: host_info['ec2_configuration_endpoint_address'] = value['Address'] host_info['ec2_configuration_endpoint_port'] = value['Port'] + + # Target: Cache Nodes and Redis Cache Clusters (single node) if key == 'ec2_endpoint' and value: host_info['ec2_endpoint_address'] = value['Address'] host_info['ec2_endpoint_port'] = value['Port'] + + # Target: Redis Replication Groups if key == 'ec2_node_groups' and value: host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address'] host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port'] @@ -1107,25 +1113,41 @@ class Ec2Inventory(object): host_info['ec2_replica_cluster_port_'+ str(replica_count)] = node['ReadEndpoint']['Port'] host_info['ec2_replica_cluster_id_'+ str(replica_count)] = node['CacheClusterId'] replica_count += 1 + + # Target: Redis Replication Groups if key == 'ec2_member_clusters' and value: host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) + + # Target: All Cache Clusters elif key == 'ec2_cache_parameter_group': host_info["ec2_cache_node_ids_to_reboot"] = ','.join([str(i) for i in value['CacheNodeIdsToReboot']]) host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] + + # Target: Almost everything elif key == 'ec2_security_groups': sg_ids = [] for sg in value: sg_ids.append(sg['SecurityGroupId']) host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids]) + + # Target: Everything + # Preserve booleans and integers elif type(value) in [int, bool]: host_info[key] = value + + # Target: Everything + # Sanitize string values elif isinstance(value, six.string_types): host_info[key] = value.strip() + + # Target: Everything + # Replace None by an empty string elif type(value) == type(None): host_info[key] = '' else: + # Remove non-processed complex types pass return host_info From e8c3e3d64520f12d3afb224f6fc5e2723535873c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Sun, 14 Jun 2015 23:38:09 +0200 Subject: [PATCH 21/26] Cleans some unnecessary white spaces in ec2.py dynamic inventory plugin --- plugins/inventory/ec2.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 9cb7219f66..2c6066fc6a 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -420,7 +420,7 @@ class Ec2Inventory(object): self.add_rds_instance(instance, region) except boto.exception.BotoServerError as e: error = e.reason - + if e.error_code == 'AuthFailure': error = self.get_auth_error_message() if not e.reason == "Forbidden": @@ -513,7 +513,7 @@ class Ec2Inventory(object): errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths)) return '\n'.join(errors) - + def fail_with_error(self, err_msg): '''log an error to std err for ansible-playbook to consume and exit''' sys.stderr.write(err_msg) @@ -1025,7 +1025,6 @@ class Ec2Inventory(object): return list(name_list) - def get_host_info_dict_from_instance(self, instance): instance_vars = {} for key in vars(instance): @@ -1225,7 +1224,6 @@ class Ec2Inventory(object): return re.sub("[^A-Za-z0-9\_]", "_", word) - def json_format_dict(self, data, pretty=False): ''' Converts a dict to a JSON object and dumps it as a formatted string ''' From f2d22c1373fe80b19a18a0e91eec7e892a4788da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Mon, 15 Jun 2015 10:02:54 +0200 Subject: [PATCH 22/26] Fixes error messages to mention ElastiCache --- plugins/inventory/ec2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 2c6066fc6a..3f0b950986 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -447,7 +447,7 @@ class Ec2Inventory(object): if e.error_code == 'AuthFailure': error = self.get_auth_error_message() if not e.reason == "Forbidden": - error = "Looks like AWS RDS is down:\n%s" % e.message + error = "Looks like AWS ElastiCache is down:\n%s" % e.message self.fail_with_error(error) try: @@ -481,7 +481,7 @@ class Ec2Inventory(object): if e.error_code == 'AuthFailure': error = self.get_auth_error_message() if not e.reason == "Forbidden": - error = "Looks like AWS RDS is down:\n%s" % e.message + error = "Looks like AWS ElastiCache [Replication Groups] is down:\n%s" % e.message self.fail_with_error(error) try: @@ -491,7 +491,7 @@ class Ec2Inventory(object): replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups'] except KeyError as e: - error = "ElastiCache query to AWS failed (unexpected format)." + error = "ElastiCache [Replication Groups] query to AWS failed (unexpected format)." self.fail_with_error(error) for replication_group in replication_groups: From 2acfbce64de08a623598443547e090e7ca987e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Mon, 15 Jun 2015 11:35:25 +0200 Subject: [PATCH 23/26] Removes unnecessary commented code and replaces with useful information --- plugins/inventory/ec2.py | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 3f0b950986..e07efac4c0 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -786,12 +786,8 @@ class Ec2Inventory(object): if self.nested_groups: self.push_group(self.inventory, 'types', type_name) - # Inventory: Group by VPC - # if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id: - # vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id) - # self.push(self.inventory, vpc_id_name, dest) - # if self.nested_groups: - # self.push_group(self.inventory, 'vpcs', vpc_id_name) + # Inventory: Group by VPC (information not available in the current + # AWS API version for ElastiCache) # Inventory: Group by security group if self.group_by_security_group and not is_redis: @@ -878,12 +874,8 @@ class Ec2Inventory(object): if self.nested_groups: self.push_group(self.inventory, 'types', type_name) - # Inventory: Group by VPC - # if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id: - # vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id) - # self.push(self.inventory, vpc_id_name, dest) - # if self.nested_groups: - # self.push_group(self.inventory, 'vpcs', vpc_id_name) + # Inventory: Group by VPC (information not available in the current + # AWS API version for ElastiCache) # Inventory: Group by security group if self.group_by_security_group: @@ -900,17 +892,9 @@ class Ec2Inventory(object): if self.nested_groups: self.push_group(self.inventory, 'elasticache_engines', self.to_safe("elasticache_" + cluster['Engine'])) - # Inventory: Group by parameter group - # if self.group_by_elasticache_parameter_group: - # self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest) - # if self.nested_groups: - # self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName'])) + # Inventory: Group by parameter group (done at cluster level) - # Inventory: Group by replication group - # if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']: - # self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest) - # if self.nested_groups: - # self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe("elasticache_" + cluster['ReplicationGroupId'])) + # Inventory: Group by replication group (done at cluster level) # Inventory: Group by ElastiCache Cluster if self.group_by_elasticache_cluster: From d164c9c7a0f0c2c2c2db6edf3092b41f0beccaa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Mon, 15 Jun 2015 11:36:33 +0200 Subject: [PATCH 24/26] Adds explanation about all_elasticache_nodes and all_elastic_clusters settings --- plugins/inventory/ec2.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/inventory/ec2.ini b/plugins/inventory/ec2.ini index b6818e876c..c21e512c0d 100644 --- a/plugins/inventory/ec2.ini +++ b/plugins/inventory/ec2.ini @@ -65,6 +65,11 @@ all_rds_instances = False # By default, only ElastiCache clusters and nodes in the 'available' state # are returned. Set 'all_elasticache_clusters' and/or 'all_elastic_nodes' # to True return all ElastiCache clusters and nodes, regardless of state. +# +# Note that all_elasticache_nodes only applies to listed clusters. That means +# if you set all_elastic_clusters to false, no node will be return from +# unavailable clusters, regardless of the state and to what you set for +# all_elasticache_nodes. all_elasticache_replication_groups = False all_elasticache_clusters = False all_elasticache_nodes = False From d88a42570e459d962c33ceb92466f64075fdc808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Mon, 29 Jun 2015 21:56:36 +0200 Subject: [PATCH 25/26] Adds a check for 'not None' values when iterating ElastiCache SecurityGroups keys --- plugins/inventory/ec2.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index e07efac4c0..081990cd8f 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -791,7 +791,11 @@ class Ec2Inventory(object): # Inventory: Group by security group if self.group_by_security_group and not is_redis: - if 'SecurityGroups' in cluster: + + # Check for the existance of the 'SecurityGroups' key and also if + # this key has some value. When the cluster is not placed in a SG + # the query can return None here and cause an error. + if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: for security_group in cluster['SecurityGroups']: key = self.to_safe("security_group_" + security_group['SecurityGroupId']) self.push(self.inventory, key, dest) @@ -879,7 +883,11 @@ class Ec2Inventory(object): # Inventory: Group by security group if self.group_by_security_group: - if 'SecurityGroups' in cluster: + + # Check for the existance of the 'SecurityGroups' key and also if + # this key has some value. When the cluster is not placed in a SG + # the query can return None here and cause an error. + if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: for security_group in cluster['SecurityGroups']: key = self.to_safe("security_group_" + security_group['SecurityGroupId']) self.push(self.inventory, key, dest) From df77d087a52cd7ab004ef1d1b9be6606f1962f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Schr=C3=B6der?= Date: Mon, 29 Jun 2015 23:28:55 +0200 Subject: [PATCH 26/26] Adds the check for 'not None' also when building host_info dict for ElastiCache clusters, nodes and replication groups --- plugins/inventory/ec2.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index 081990cd8f..864a64f5ed 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -1117,10 +1117,14 @@ class Ec2Inventory(object): # Target: Almost everything elif key == 'ec2_security_groups': - sg_ids = [] - for sg in value: - sg_ids.append(sg['SecurityGroupId']) - host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids]) + + # Skip if SecurityGroups is None + # (it is possible to have the key defined but no value in it). + if value is not None: + sg_ids = [] + for sg in value: + sg_ids.append(sg['SecurityGroupId']) + host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids]) # Target: Everything # Preserve booleans and integers