From 03cd38e7abe057269e7b8c15e0fdedbe18e0b3f4 Mon Sep 17 00:00:00 2001 From: Mark Chance Date: Wed, 9 Sep 2015 17:09:46 -0600 Subject: [PATCH] cloud amazon ECS task definition module --- cloud/amazon/ecs_taskdefinition.py | 215 +++++++++++++++++++++++ cloud/amazon/ecs_taskdefinition_facts.py | 173 ++++++++++++++++++ 2 files changed, 388 insertions(+) create mode 100644 cloud/amazon/ecs_taskdefinition.py create mode 100644 cloud/amazon/ecs_taskdefinition_facts.py diff --git a/cloud/amazon/ecs_taskdefinition.py b/cloud/amazon/ecs_taskdefinition.py new file mode 100644 index 00000000000..9915e9d8070 --- /dev/null +++ b/cloud/amazon/ecs_taskdefinition.py @@ -0,0 +1,215 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: ecs_taskdefinition +short_description: register a task definition in ecs +description: + - Creates or terminates task definitions +version_added: "1.9" +requirements: [ json, boto, botocore, boto3 ] +options: + state: + description: + - State whether the task definition should exist or be deleted + required: true + choices=['present', 'absent'] + + arn: + description: + - The arn of the task description to delete + required: false + + family: + =dict(required=False, type='str' ), + + revision: + required: False + type: int + + containers: + required: False + type: list of dicts with container definitions + + volumes: + required: False + type: list of name +''' + +EXAMPLES = ''' +- name: "Create task definition" + ecs_taskdefinition: + containers: + - name: simple-app + cpu: 10 + essential: true + image: "httpd:2.4" + memory: 300 + mountPoints: + - containerPath: /usr/local/apache2/htdocs + sourceVolume: my-vol + portMappings: + - containerPort: 80 + hostPort: 80 + - name: busybox + command: + - "/bin/sh -c \"while true; do echo ' Amazon ECS Sample App

Amazon ECS Sample App

Congratulations!

Your application is now running on a container in Amazon ECS.

' > top; /bin/date > date ; echo '
' > bottom; cat top date bottom > /usr/local/apache2/htdocs/index.html ; sleep 1; done\"" + cpu: 10 + entryPoint: + - sh + - "-c" + essential: false + image: busybox + memory: 200 + volumesFrom: + - sourceContainer: simple-app + volumes: + - name: my-vol + family: test-cluster-taskdef + state: present + register: task_output +''' +RETURN = ''' +taskdefinition: + description: a reflection of the input parameters + type: dict inputs plus revision, status, taskDefinitionArn +''' +try: + import json + import boto + import botocore + HAS_BOTO = True +except ImportError: + HAS_BOTO = False + +try: + import boto3 + HAS_BOTO3 = True +except ImportError: + HAS_BOTO3 = False + +class EcsTaskManager: + """Handles ECS Tasks""" + + def __init__(self, module): + self.module = module + + try: + region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) + 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") + self.ecs = boto3_conn(module, conn_type='client', resource='ecs', region=region, endpoint=ec2_url, **aws_connect_kwargs) + except boto.exception.NoAuthHandlerFound, e: + self.module.fail_json(msg=str(e)) + + def describe_task(self, task_name): + try: + response = self.ecs.describe_task_definition(taskDefinition=task_name) + return response['taskDefinition'] + except botocore.exceptions.ClientError: + return None + + def register_task(self, family, container_definitions, volumes): + response = self.ecs.register_task_definition(family=family, + containerDefinitions=container_definitions, volumes=volumes) + return response['taskDefinition'] + + def deregister_task(self, taskArn): + response = self.ecs.deregister_task_definition(taskDefinition=taskArn) + return response['taskDefinition'] + +def main(): + + argument_spec = ec2_argument_spec() + argument_spec.update(dict( + state=dict(required=True, choices=['present', 'absent'] ), + arn=dict(required=False, type='str' ), + family=dict(required=False, type='str' ), + revision=dict(required=False, type='int' ), + containers=dict(required=False, type='list' ), + volumes=dict(required=False, type='list' ) + )) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_BOTO: + module.fail_json(msg='boto is required.') + + if not HAS_BOTO3: + module.fail_json(msg='boto3 is required.') + + task_to_describe = None + # When deregistering a task, we can specify the ARN OR + # the family and revision. + if module.params['state'] == 'absent': + if 'arn' in module.params and module.params['arn'] is not None: + task_to_describe = module.params['arn'] + elif 'family' in module.params and module.params['family'] is not None and 'revision' in module.params and module.params['revision'] is not None: + task_to_describe = module.params['family']+":"+str(module.params['revision']) + else: + module.fail_json(msg="To use task definitions, an arn or family and revision must be specified") + # When registering a task, we can specify the ARN OR + # the family and revision. + if module.params['state'] == 'present': + if not 'family' in module.params: + module.fail_json(msg="To use task definitions, a family must be specified") + if not 'containers' in module.params: + module.fail_json(msg="To use task definitions, a list of containers must be specified") + task_to_describe = module.params['family'] + + task_mgr = EcsTaskManager(module) + existing = task_mgr.describe_task(task_to_describe) + + results = dict(changed=False) + if module.params['state'] == 'present': + if existing and 'status' in existing and existing['status']=="ACTIVE": + results['taskdefinition']=existing + else: + if not module.check_mode: + # doesn't exist. create it. + volumes = [] + if 'volumes' in module.params: + volumes = module.params['volumes'] + if volumes is None: + volumes = [] + results['taskdefinition'] = task_mgr.register_task(module.params['family'], + module.params['containers'], volumes) + results['changed'] = True + + # delete the cloudtrai + elif module.params['state'] == 'absent': + if not existing: + pass + else: + # it exists, so we should delete it and mark changed. + # return info about the cluster deleted + results['taskdefinition'] = existing + if 'status' in existing and existing['status']=="INACTIVE": + results['changed'] = False + else: + if not module.check_mode: + task_mgr.deregister_task(task_to_describe) + results['changed'] = True + + module.exit_json(**results) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import * + +if __name__ == '__main__': + main() diff --git a/cloud/amazon/ecs_taskdefinition_facts.py b/cloud/amazon/ecs_taskdefinition_facts.py new file mode 100644 index 00000000000..c351639513f --- /dev/null +++ b/cloud/amazon/ecs_taskdefinition_facts.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: ecs_taskdefinition_facts +short_description: return facts about task definitions in ecs +description: + - Describes or lists task definitions. +version_added: 1.9 +requirements: [ json, os, boto, botocore, boto3 ] +options: + details: + description: + - Set this to true if you want detailed information about the tasks. + required: false + default: false + name: + description: + - When details is true, the name must be provided. + required: false + family: + description: + - the name of the family of task definitions to list. + required: false + max_results: + description: + - The maximum number of results to return. + required: false + status: + description: + - Show only task descriptions of the given status. If omitted, it shows all + required: false + choices: ['ACTIVE', 'INACTIVE'] + sort: + description: + - Sort order of returned list of task definitions + required: false + choices: ['ASC', 'DESC'] + +''' + +EXAMPLES = ''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +# Basic listing example +- name: "Get task definitions with details" + ecs_taskdefinition_facts: + name: test-cluster-tasks + details: true + +- name: Get task definitions with details + ecs_taskdefinition_facts: + status: INACTIVE + details: true + family: test-cluster-rbjgjoaj-task + name: "arn:aws:ecs:us-west-2:172139249013:task-definition/test-cluster-rbjgjoaj-task:1" +''' +RETURN = ''' +task_definitions: + description: array of ARN values for the known task definitions + type: array of string or dict if details is true + sample: ["arn:aws:ecs:us-west-2:172139249013:task-definition/console-sample-app-static:1"] +''' +try: + import json, os + import boto + import botocore + # import module snippets + from ansible.module_utils.basic import * + from ansible.module_utils.ec2 import * + HAS_BOTO = True +except ImportError: + HAS_BOTO = False + +try: + import boto3 + HAS_BOTO3 = True +except ImportError: + HAS_BOTO3 = False + +class EcsTaskManager: + """Handles ECS Tasks""" + + def __init__(self, module): + self.module = module + + try: + # self.ecs = boto3.client('ecs') + region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) + 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") + self.ecs = boto3_conn(module, conn_type='client', resource='ecs', region=region, endpoint=ec2_url, **aws_connect_kwargs) + except boto.exception.NoAuthHandlerFound, e: + self.module.fail_json(msg=str(e)) + + def transmogrify(self, params, field, dictionary, arg_name): + if field in params and params[field] is not None: + dictionary[arg_name] = params[field] + + def list_taskdefinitions(self, params): + fn_args = dict() + self.transmogrify(params, 'family', fn_args, 'familyPrefix') + self.transmogrify(params, 'max_results', fn_args, 'maxResults') + self.transmogrify(params, 'status', fn_args, 'status') + self.transmogrify(params, 'sort', fn_args, 'sort') + response = self.ecs.list_task_definitions(**fn_args) + return dict(task_definitions=response['taskDefinitionArns']) + + def describe_taskdefinition(self, task_definition): + try: + response = self.ecs.describe_task_definition(taskDefinition=task_definition) + except botocore.exceptions.ClientError: + response = dict(taskDefinition=[ dict( name=task_definition, status="MISSING")]) + relevant_response = dict( + task_definitions = response['taskDefinition'] + ) + return relevant_response + +def main(): + + argument_spec = ec2_argument_spec() + argument_spec.update(dict( + details=dict(required= False, type='bool' ), + name=dict(required=False, type='str' ), + family=dict(required=False, type='str' ), + max_results=dict(required=False, type='int' ), + status=dict(required=False, choices=['ACTIVE', 'INACTIVE']), + sort=dict(required=False, choices=['ASC', 'DESC']) + )) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if not HAS_BOTO: + module.fail_json(msg='boto is required.') + + if not HAS_BOTO3: + module.fail_json(msg='boto3 is required.') + + show_details = False + if 'details' in module.params and module.params['details']: + if 'name' not in module.params or not module.params['name']: + module.fail_json(msg="task definition name must be specified for ecs_taskdefinition_facts") + show_details = True + + task_mgr = EcsTaskManager(module) + if show_details: + ecs_facts = task_mgr.describe_taskdefinition(module.params['name']) + else: + ecs_facts = task_mgr.list_taskdefinitions(module.params) + ecs_facts_result = dict(changed=False, ansible_facts=ecs_facts) + module.exit_json(**ecs_facts_result) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import * +from ansible.module_utils.urls import * + +if __name__ == '__main__': + main()