1#!/usr/bin/python 2# Copyright: Ansible Project 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4 5from __future__ import absolute_import, division, print_function 6__metaclass__ = type 7 8ANSIBLE_METADATA = {'metadata_version': '1.1', 9 'status': ['preview'], 10 'supported_by': 'community'} 11 12 13DOCUMENTATION = ''' 14--- 15module: ecs_service_info 16short_description: list or describe services in ecs 17description: 18 - Lists or describes services in ecs. 19 - This module was called C(ecs_service_facts) before Ansible 2.9, returning C(ansible_facts). 20 Note that the M(ecs_service_info) module no longer returns C(ansible_facts)! 21version_added: "2.1" 22author: 23 - "Mark Chance (@Java1Guy)" 24 - "Darek Kaczynski (@kaczynskid)" 25requirements: [ json, botocore, boto3 ] 26options: 27 details: 28 description: 29 - Set this to true if you want detailed information about the services. 30 required: false 31 default: 'false' 32 type: bool 33 events: 34 description: 35 - Whether to return ECS service events. Only has an effect if C(details) is true. 36 required: false 37 default: 'true' 38 type: bool 39 version_added: "2.6" 40 cluster: 41 description: 42 - The cluster ARNS in which to list the services. 43 required: false 44 default: 'default' 45 service: 46 description: 47 - One or more services to get details for 48 required: false 49extends_documentation_fragment: 50 - aws 51 - ec2 52''' 53 54EXAMPLES = ''' 55# Note: These examples do not set authentication details, see the AWS Guide for details. 56 57# Basic listing example 58- ecs_service_info: 59 cluster: test-cluster 60 service: console-test-service 61 details: true 62 register: output 63 64# Basic listing example 65- ecs_service_info: 66 cluster: test-cluster 67 register: output 68''' 69 70RETURN = ''' 71services: 72 description: When details is false, returns an array of service ARNs, otherwise an array of complex objects as described below. 73 returned: success 74 type: complex 75 contains: 76 clusterArn: 77 description: The Amazon Resource Name (ARN) of the of the cluster that hosts the service. 78 returned: always 79 type: str 80 desiredCount: 81 description: The desired number of instantiations of the task definition to keep running on the service. 82 returned: always 83 type: int 84 loadBalancers: 85 description: A list of load balancer objects 86 returned: always 87 type: complex 88 contains: 89 loadBalancerName: 90 description: the name 91 returned: always 92 type: str 93 containerName: 94 description: The name of the container to associate with the load balancer. 95 returned: always 96 type: str 97 containerPort: 98 description: The port on the container to associate with the load balancer. 99 returned: always 100 type: int 101 pendingCount: 102 description: The number of tasks in the cluster that are in the PENDING state. 103 returned: always 104 type: int 105 runningCount: 106 description: The number of tasks in the cluster that are in the RUNNING state. 107 returned: always 108 type: int 109 serviceArn: 110 description: The Amazon Resource Name (ARN) that identifies the service. The ARN contains the arn:aws:ecs namespace, followed by the region of the service, the AWS account ID of the service owner, the service namespace, and then the service name. For example, arn:aws:ecs:region :012345678910 :service/my-service . 111 returned: always 112 type: str 113 serviceName: 114 description: A user-generated string used to identify the service 115 returned: always 116 type: str 117 status: 118 description: The valid values are ACTIVE, DRAINING, or INACTIVE. 119 returned: always 120 type: str 121 taskDefinition: 122 description: The ARN of a task definition to use for tasks in the service. 123 returned: always 124 type: str 125 deployments: 126 description: list of service deployments 127 returned: always 128 type: list of complex 129 events: 130 description: list of service events 131 returned: when events is true 132 type: list of complex 133''' # NOQA 134 135try: 136 import botocore 137except ImportError: 138 pass # handled by AnsibleAWSModule 139 140from ansible.module_utils.aws.core import AnsibleAWSModule 141from ansible.module_utils.ec2 import ec2_argument_spec, AWSRetry 142 143 144class EcsServiceManager: 145 """Handles ECS Services""" 146 147 def __init__(self, module): 148 self.module = module 149 self.ecs = module.client('ecs') 150 151 @AWSRetry.backoff(tries=5, delay=5, backoff=2.0) 152 def list_services_with_backoff(self, **kwargs): 153 paginator = self.ecs.get_paginator('list_services') 154 try: 155 return paginator.paginate(**kwargs).build_full_result() 156 except botocore.exceptions.ClientError as e: 157 if e.response['Error']['Code'] == 'ClusterNotFoundException': 158 self.module.fail_json_aws(e, "Could not find cluster to list services") 159 else: 160 raise 161 162 @AWSRetry.backoff(tries=5, delay=5, backoff=2.0) 163 def describe_services_with_backoff(self, **kwargs): 164 return self.ecs.describe_services(**kwargs) 165 166 def list_services(self, cluster): 167 fn_args = dict() 168 if cluster and cluster is not None: 169 fn_args['cluster'] = cluster 170 try: 171 response = self.list_services_with_backoff(**fn_args) 172 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 173 self.module.fail_json_aws(e, msg="Couldn't list ECS services") 174 relevant_response = dict(services=response['serviceArns']) 175 return relevant_response 176 177 def describe_services(self, cluster, services): 178 fn_args = dict() 179 if cluster and cluster is not None: 180 fn_args['cluster'] = cluster 181 fn_args['services'] = services 182 try: 183 response = self.describe_services_with_backoff(**fn_args) 184 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 185 self.module.fail_json_aws(e, msg="Couldn't describe ECS services") 186 running_services = [self.extract_service_from(service) for service in response.get('services', [])] 187 services_not_running = response.get('failures', []) 188 return running_services, services_not_running 189 190 def extract_service_from(self, service): 191 # some fields are datetime which is not JSON serializable 192 # make them strings 193 if 'deployments' in service: 194 for d in service['deployments']: 195 if 'createdAt' in d: 196 d['createdAt'] = str(d['createdAt']) 197 if 'updatedAt' in d: 198 d['updatedAt'] = str(d['updatedAt']) 199 if 'events' in service: 200 if not self.module.params['events']: 201 del service['events'] 202 else: 203 for e in service['events']: 204 if 'createdAt' in e: 205 e['createdAt'] = str(e['createdAt']) 206 return service 207 208 209def chunks(l, n): 210 """Yield successive n-sized chunks from l.""" 211 """ https://stackoverflow.com/a/312464 """ 212 for i in range(0, len(l), n): 213 yield l[i:i + n] 214 215 216def main(): 217 218 argument_spec = ec2_argument_spec() 219 argument_spec.update(dict( 220 details=dict(type='bool', default=False), 221 events=dict(type='bool', default=True), 222 cluster=dict(), 223 service=dict(type='list') 224 )) 225 226 module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) 227 is_old_facts = module._name == 'ecs_service_facts' 228 if is_old_facts: 229 module.deprecate("The 'ecs_service_facts' module has been renamed to 'ecs_service_info', " 230 "and the renamed one no longer returns ansible_facts", version='2.13') 231 232 show_details = module.params.get('details') 233 234 task_mgr = EcsServiceManager(module) 235 if show_details: 236 if module.params['service']: 237 services = module.params['service'] 238 else: 239 services = task_mgr.list_services(module.params['cluster'])['services'] 240 ecs_info = dict(services=[], services_not_running=[]) 241 for chunk in chunks(services, 10): 242 running_services, services_not_running = task_mgr.describe_services(module.params['cluster'], chunk) 243 ecs_info['services'].extend(running_services) 244 ecs_info['services_not_running'].extend(services_not_running) 245 else: 246 ecs_info = task_mgr.list_services(module.params['cluster']) 247 248 if is_old_facts: 249 module.exit_json(changed=False, ansible_facts=ecs_info, **ecs_info) 250 else: 251 module.exit_json(changed=False, **ecs_info) 252 253 254if __name__ == '__main__': 255 main() 256