|
|
@ -33,6 +33,9 @@ version_added: "2.1"
|
|
|
|
author:
|
|
|
|
author:
|
|
|
|
- "Mark Chance (@java1guy)"
|
|
|
|
- "Mark Chance (@java1guy)"
|
|
|
|
- "Darek Kaczynski (@kaczynskid)"
|
|
|
|
- "Darek Kaczynski (@kaczynskid)"
|
|
|
|
|
|
|
|
- "Stephane Maarek (@simplesteph)"
|
|
|
|
|
|
|
|
- "Zac Blazic (@zacblazic)"
|
|
|
|
|
|
|
|
|
|
|
|
requirements: [ json, boto, botocore, boto3 ]
|
|
|
|
requirements: [ json, boto, botocore, boto3 ]
|
|
|
|
options:
|
|
|
|
options:
|
|
|
|
state:
|
|
|
|
state:
|
|
|
@ -78,6 +81,11 @@ options:
|
|
|
|
- The number of times to check that the service is available
|
|
|
|
- The number of times to check that the service is available
|
|
|
|
required: false
|
|
|
|
required: false
|
|
|
|
default: 10
|
|
|
|
default: 10
|
|
|
|
|
|
|
|
deployment_configuration:
|
|
|
|
|
|
|
|
description:
|
|
|
|
|
|
|
|
- Optional parameters that control the deployment_configuration; format is '{"maximum_percent":<integer>, "minimum_healthy_percent":<integer>}
|
|
|
|
|
|
|
|
required: false
|
|
|
|
|
|
|
|
version_added: 2.3
|
|
|
|
extends_documentation_fragment:
|
|
|
|
extends_documentation_fragment:
|
|
|
|
- aws
|
|
|
|
- aws
|
|
|
|
- ec2
|
|
|
|
- ec2
|
|
|
@ -103,6 +111,17 @@ EXAMPLES = '''
|
|
|
|
name: default
|
|
|
|
name: default
|
|
|
|
state: absent
|
|
|
|
state: absent
|
|
|
|
cluster: new_cluster
|
|
|
|
cluster: new_cluster
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# With custom deployment configuration
|
|
|
|
|
|
|
|
- ecs_service:
|
|
|
|
|
|
|
|
name: test-service
|
|
|
|
|
|
|
|
cluster: test-cluster
|
|
|
|
|
|
|
|
task_definition: test-task-definition
|
|
|
|
|
|
|
|
desired_count: 3
|
|
|
|
|
|
|
|
deployment_configuration:
|
|
|
|
|
|
|
|
minimum_healthy_percent: 75
|
|
|
|
|
|
|
|
maximum_percent: 150
|
|
|
|
|
|
|
|
state: present
|
|
|
|
'''
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
RETURN = '''
|
|
|
@ -164,6 +183,19 @@ service:
|
|
|
|
description: list of service deployments
|
|
|
|
description: list of service deployments
|
|
|
|
returned: always
|
|
|
|
returned: always
|
|
|
|
type: list of complex
|
|
|
|
type: list of complex
|
|
|
|
|
|
|
|
deploymentConfiguration:
|
|
|
|
|
|
|
|
description: dictionary of deploymentConfiguration
|
|
|
|
|
|
|
|
returned: always
|
|
|
|
|
|
|
|
type: complex
|
|
|
|
|
|
|
|
contains:
|
|
|
|
|
|
|
|
maximumPercent:
|
|
|
|
|
|
|
|
description: maximumPercent param
|
|
|
|
|
|
|
|
returned: always
|
|
|
|
|
|
|
|
type: int
|
|
|
|
|
|
|
|
minimumHealthyPercent:
|
|
|
|
|
|
|
|
description: minimumHealthyPercent param
|
|
|
|
|
|
|
|
returned: always
|
|
|
|
|
|
|
|
type: int
|
|
|
|
events:
|
|
|
|
events:
|
|
|
|
description: lost of service events
|
|
|
|
description: lost of service events
|
|
|
|
returned: always
|
|
|
|
returned: always
|
|
|
@ -180,6 +212,55 @@ ansible_facts:
|
|
|
|
'''
|
|
|
|
'''
|
|
|
|
import time
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEPLOYMENT_CONFIGURATION_TYPE_MAP = {
|
|
|
|
|
|
|
|
'maximum_percent': 'int',
|
|
|
|
|
|
|
|
'minimum_healthy_percent': 'int'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TypeMapper:
|
|
|
|
|
|
|
|
def map_complex_type(self, complex_type, type_map):
|
|
|
|
|
|
|
|
if complex_type is None:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
new_type = type(complex_type)()
|
|
|
|
|
|
|
|
if isinstance(complex_type, dict):
|
|
|
|
|
|
|
|
for key in complex_type:
|
|
|
|
|
|
|
|
if key in type_map:
|
|
|
|
|
|
|
|
if isinstance(type_map[key], list):
|
|
|
|
|
|
|
|
new_type[key] = self.map_complex_type(
|
|
|
|
|
|
|
|
complex_type[key],
|
|
|
|
|
|
|
|
type_map[key][0])
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
new_type[key] = self.map_complex_type(
|
|
|
|
|
|
|
|
complex_type[key],
|
|
|
|
|
|
|
|
type_map[key])
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return complex_type
|
|
|
|
|
|
|
|
elif isinstance(complex_type, list):
|
|
|
|
|
|
|
|
for i in range(len(complex_type)):
|
|
|
|
|
|
|
|
new_type.append(self.map_complex_type(
|
|
|
|
|
|
|
|
complex_type[i],
|
|
|
|
|
|
|
|
type_map))
|
|
|
|
|
|
|
|
elif type_map:
|
|
|
|
|
|
|
|
return vars(globals()['__builtins__'])[type_map](complex_type)
|
|
|
|
|
|
|
|
return new_type
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def camelize(self, complex_type):
|
|
|
|
|
|
|
|
if complex_type is None:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
new_type = type(complex_type)()
|
|
|
|
|
|
|
|
if isinstance(complex_type, dict):
|
|
|
|
|
|
|
|
for key in complex_type:
|
|
|
|
|
|
|
|
new_type[self.camel(key)] = self.camelize(complex_type[key])
|
|
|
|
|
|
|
|
elif isinstance(complex_type, list):
|
|
|
|
|
|
|
|
for i in range(len(complex_type)):
|
|
|
|
|
|
|
|
new_type.append(self.camelize(complex_type[i]))
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return complex_type
|
|
|
|
|
|
|
|
return new_type
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def camel(self, words):
|
|
|
|
|
|
|
|
return words.split('_')[0] + ''.join(x.capitalize() or '_' for x in words.split('_')[1:])
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
import boto
|
|
|
|
import boto
|
|
|
|
import botocore
|
|
|
|
import botocore
|
|
|
@ -204,7 +285,6 @@ class EcsServiceManager:
|
|
|
|
self.module = module
|
|
|
|
self.module = module
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
# self.ecs = boto3.client('ecs')
|
|
|
|
|
|
|
|
region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)
|
|
|
|
region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)
|
|
|
|
if not region:
|
|
|
|
if not region:
|
|
|
|
module.fail_json(msg="Region must be specified as a parameter, in EC2_REGION or AWS_REGION environment variables or in boto configuration file")
|
|
|
|
module.fail_json(msg="Region must be specified as a parameter, in EC2_REGION or AWS_REGION environment variables or in boto configuration file")
|
|
|
@ -212,15 +292,6 @@ class EcsServiceManager:
|
|
|
|
except boto.exception.NoAuthHandlerFound as e:
|
|
|
|
except boto.exception.NoAuthHandlerFound as e:
|
|
|
|
self.module.fail_json(msg="Can't authorize connection - %s" % str(e))
|
|
|
|
self.module.fail_json(msg="Can't authorize connection - %s" % str(e))
|
|
|
|
|
|
|
|
|
|
|
|
# def list_clusters(self):
|
|
|
|
|
|
|
|
# return self.client.list_clusters()
|
|
|
|
|
|
|
|
# {'failures=[],
|
|
|
|
|
|
|
|
# 'ResponseMetadata={'HTTPStatusCode=200, 'RequestId='ce7b5880-1c41-11e5-8a31-47a93a8a98eb'},
|
|
|
|
|
|
|
|
# 'clusters=[{'activeServicesCount=0, 'clusterArn='arn:aws:ecs:us-west-2:777110527155:cluster/default', 'status='ACTIVE', 'pendingTasksCount=0, 'runningTasksCount=0, 'registeredContainerInstancesCount=0, 'clusterName='default'}]}
|
|
|
|
|
|
|
|
# {'failures=[{'arn='arn:aws:ecs:us-west-2:777110527155:cluster/bogus', 'reason='MISSING'}],
|
|
|
|
|
|
|
|
# 'ResponseMetadata={'HTTPStatusCode=200, 'RequestId='0f66c219-1c42-11e5-8a31-47a93a8a98eb'},
|
|
|
|
|
|
|
|
# 'clusters=[]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_in_array(self, array_of_services, service_name, field_name='serviceArn'):
|
|
|
|
def find_in_array(self, array_of_services, service_name, field_name='serviceArn'):
|
|
|
|
for c in array_of_services:
|
|
|
|
for c in array_of_services:
|
|
|
|
if c[field_name].endswith(service_name):
|
|
|
|
if c[field_name].endswith(service_name):
|
|
|
@ -259,7 +330,7 @@ class EcsServiceManager:
|
|
|
|
return True
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def create_service(self, service_name, cluster_name, task_definition,
|
|
|
|
def create_service(self, service_name, cluster_name, task_definition,
|
|
|
|
load_balancers, desired_count, client_token, role):
|
|
|
|
load_balancers, desired_count, client_token, role, deployment_configuration):
|
|
|
|
response = self.ecs.create_service(
|
|
|
|
response = self.ecs.create_service(
|
|
|
|
cluster=cluster_name,
|
|
|
|
cluster=cluster_name,
|
|
|
|
serviceName=service_name,
|
|
|
|
serviceName=service_name,
|
|
|
@ -267,16 +338,18 @@ class EcsServiceManager:
|
|
|
|
loadBalancers=load_balancers,
|
|
|
|
loadBalancers=load_balancers,
|
|
|
|
desiredCount=desired_count,
|
|
|
|
desiredCount=desired_count,
|
|
|
|
clientToken=client_token,
|
|
|
|
clientToken=client_token,
|
|
|
|
role=role)
|
|
|
|
role=role,
|
|
|
|
|
|
|
|
deploymentConfiguration=deployment_configuration)
|
|
|
|
return self.jsonize(response['service'])
|
|
|
|
return self.jsonize(response['service'])
|
|
|
|
|
|
|
|
|
|
|
|
def update_service(self, service_name, cluster_name, task_definition,
|
|
|
|
def update_service(self, service_name, cluster_name, task_definition,
|
|
|
|
load_balancers, desired_count, client_token, role):
|
|
|
|
load_balancers, desired_count, client_token, role, deployment_configuration):
|
|
|
|
response = self.ecs.update_service(
|
|
|
|
response = self.ecs.update_service(
|
|
|
|
cluster=cluster_name,
|
|
|
|
cluster=cluster_name,
|
|
|
|
service=service_name,
|
|
|
|
service=service_name,
|
|
|
|
taskDefinition=task_definition,
|
|
|
|
taskDefinition=task_definition,
|
|
|
|
desiredCount=desired_count)
|
|
|
|
desiredCount=desired_count,
|
|
|
|
|
|
|
|
deploymentConfiguration=deployment_configuration)
|
|
|
|
return self.jsonize(response['service'])
|
|
|
|
return self.jsonize(response['service'])
|
|
|
|
|
|
|
|
|
|
|
|
def jsonize(self, service):
|
|
|
|
def jsonize(self, service):
|
|
|
@ -297,7 +370,6 @@ class EcsServiceManager:
|
|
|
|
def delete_service(self, service, cluster=None):
|
|
|
|
def delete_service(self, service, cluster=None):
|
|
|
|
return self.ecs.delete_service(cluster=cluster, service=service)
|
|
|
|
return self.ecs.delete_service(cluster=cluster, service=service)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
def main():
|
|
|
|
|
|
|
|
|
|
|
|
argument_spec = ec2_argument_spec()
|
|
|
|
argument_spec = ec2_argument_spec()
|
|
|
@ -311,7 +383,8 @@ def main():
|
|
|
|
client_token=dict(required=False, default='', type='str'),
|
|
|
|
client_token=dict(required=False, default='', type='str'),
|
|
|
|
role=dict(required=False, default='', type='str'),
|
|
|
|
role=dict(required=False, default='', type='str'),
|
|
|
|
delay=dict(required=False, type='int', default=10),
|
|
|
|
delay=dict(required=False, type='int', default=10),
|
|
|
|
repeat=dict(required=False, type='int', default=10)
|
|
|
|
repeat=dict(required=False, type='int', default=10),
|
|
|
|
|
|
|
|
deployment_configuration=dict(required=False, default={}, type='dict')
|
|
|
|
))
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
module = AnsibleModule(argument_spec=argument_spec,
|
|
|
|
module = AnsibleModule(argument_spec=argument_spec,
|
|
|
@ -329,6 +402,12 @@ def main():
|
|
|
|
module.fail_json(msg='boto3 is required.')
|
|
|
|
module.fail_json(msg='boto3 is required.')
|
|
|
|
|
|
|
|
|
|
|
|
service_mgr = EcsServiceManager(module)
|
|
|
|
service_mgr = EcsServiceManager(module)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type_mapper = TypeMapper()
|
|
|
|
|
|
|
|
deployment_configuration = type_mapper.map_complex_type(module.params['deployment_configuration'],
|
|
|
|
|
|
|
|
DEPLOYMENT_CONFIGURATION_TYPE_MAP)
|
|
|
|
|
|
|
|
deployment_configuration = type_mapper.camelize(deployment_configuration)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
existing = service_mgr.describe_service(module.params['cluster'], module.params['name'])
|
|
|
|
existing = service_mgr.describe_service(module.params['cluster'], module.params['name'])
|
|
|
|
except Exception as e:
|
|
|
|
except Exception as e:
|
|
|
@ -360,7 +439,8 @@ def main():
|
|
|
|
loadBalancers,
|
|
|
|
loadBalancers,
|
|
|
|
module.params['desired_count'],
|
|
|
|
module.params['desired_count'],
|
|
|
|
clientToken,
|
|
|
|
clientToken,
|
|
|
|
role)
|
|
|
|
role,
|
|
|
|
|
|
|
|
deployment_configuration)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
# doesn't exist. create it.
|
|
|
|
# doesn't exist. create it.
|
|
|
|
response = service_mgr.create_service(module.params['name'],
|
|
|
|
response = service_mgr.create_service(module.params['name'],
|
|
|
@ -369,7 +449,8 @@ def main():
|
|
|
|
loadBalancers,
|
|
|
|
loadBalancers,
|
|
|
|
module.params['desired_count'],
|
|
|
|
module.params['desired_count'],
|
|
|
|
clientToken,
|
|
|
|
clientToken,
|
|
|
|
role)
|
|
|
|
role,
|
|
|
|
|
|
|
|
deployment_configuration)
|
|
|
|
|
|
|
|
|
|
|
|
results['service'] = response
|
|
|
|
results['service'] = response
|
|
|
|
|
|
|
|
|
|
|
|