Add tags to aws ecs task (#53717)

* Add tags to ecs_task

remove older reference to credentials

* uncomment shell commands to enable/disable account settings

* Fix documentation and pep8 issues

* fix review items for ecs_task tags

use missing_required_lib for tags
change fail_json message to suggested message
switch from task_tags to tags for consisitency

* Add import for missing_required_lib function

* Tidy put-account-setting tasks and add permission

Using `environment` and `command` rather than `shell` avoids the
need for `no_log` and means that people can fix the problem

* update version added for ecs_task tags

* fix tests after removal of ansible_facts from ecs_service_info, add delay when service is still draining

* Add documentation for sanity tests
This commit is contained in:
Tad Merchant 2019-12-13 18:54:44 -05:00 committed by Jill R
parent 1535d9fd90
commit ab54736e12
4 changed files with 168 additions and 15 deletions

View file

@ -84,6 +84,12 @@ options:
version_added: 2.8
choices: ["EC2", "FARGATE"]
type: str
tags:
type: dict
description:
- Tags that will be added to ecs tasks on start and run
required: false
version_added: "2.10"
extends_documentation_fragment:
- aws
- ec2
@ -108,6 +114,11 @@ EXAMPLES = '''
cluster: console-sample-app-static-cluster
task_definition: console-sample-app-static-taskdef
task: "arn:aws:ecs:us-west-2:172139249013:task/3f8353d1-29a8-4689-bbf6-ad79937ffe8a"
tags:
resourceName: a_task_for_ansible_to_run
type: long_running_task
network: internal
version: 1.4
container_instances:
- arn:aws:ecs:us-west-2:172139249013:container-instance/79c23f22-876c-438a-bddf-55c98a3538a8
started_by: ansible_user
@ -209,7 +220,8 @@ task:
'''
from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.ec2 import ec2_argument_spec, get_ec2_security_group_ids_from_names
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.ec2 import ec2_argument_spec, get_ec2_security_group_ids_from_names, ansible_dict_to_boto3_tag_list
try:
import botocore
@ -254,7 +266,7 @@ class EcsExecManager:
return c
return None
def run_task(self, cluster, task_definition, overrides, count, startedBy, launch_type):
def run_task(self, cluster, task_definition, overrides, count, startedBy, launch_type, tags):
if overrides is None:
overrides = dict()
params = dict(cluster=cluster, taskDefinition=task_definition,
@ -263,6 +275,10 @@ class EcsExecManager:
params['networkConfiguration'] = self.format_network_configuration(self.module.params['network_configuration'])
if launch_type:
params['launchType'] = launch_type
if tags:
params['tags'] = ansible_dict_to_boto3_tag_list(tags, 'key', 'value')
# TODO: need to check if long arn format enabled.
try:
response = self.ecs.run_task(**params)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
@ -270,7 +286,7 @@ class EcsExecManager:
# include tasks and failures
return response['tasks']
def start_task(self, cluster, task_definition, overrides, container_instances, startedBy):
def start_task(self, cluster, task_definition, overrides, container_instances, startedBy, tags):
args = dict()
if cluster:
args['cluster'] = cluster
@ -284,6 +300,8 @@ class EcsExecManager:
args['startedBy'] = startedBy
if self.module.params['network_configuration']:
args['networkConfiguration'] = self.format_network_configuration(self.module.params['network_configuration'])
if tags:
args['tags'] = ansible_dict_to_boto3_tag_list(tags, 'key', 'value')
try:
response = self.ecs.start_task(**args)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
@ -302,6 +320,17 @@ class EcsExecManager:
# to e.g. ecs.run_task, it's just passed as a keyword argument)
return LooseVersion(botocore.__version__) >= LooseVersion('1.8.4')
def ecs_task_long_format_enabled(self):
account_support = self.ecs.list_account_settings(name='taskLongArnFormat', effectiveSettings=True)
return account_support['settings'][0]['value'] == 'enabled'
def ecs_api_handles_tags(self):
from distutils.version import LooseVersion
# There doesn't seem to be a nice way to inspect botocore to look
# for attributes (and networkConfiguration is not an explicit argument
# to e.g. ecs.run_task, it's just passed as a keyword argument)
return LooseVersion(botocore.__version__) >= LooseVersion('1.12.46')
def ecs_api_handles_network_configuration(self):
from distutils.version import LooseVersion
# There doesn't seem to be a nice way to inspect botocore to look
@ -322,7 +351,8 @@ def main():
container_instances=dict(required=False, type='list'), # S*
started_by=dict(required=False, type='str'), # R S
network_configuration=dict(required=False, type='dict'),
launch_type=dict(required=False, choices=['EC2', 'FARGATE'])
launch_type=dict(required=False, choices=['EC2', 'FARGATE']),
tags=dict(required=False, type='dict')
))
module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True,
@ -359,6 +389,12 @@ def main():
if module.params['launch_type'] and not service_mgr.ecs_api_handles_launch_type():
module.fail_json(msg='botocore needs to be version 1.8.4 or higher to use launch type')
if module.params['tags']:
if not service_mgr.ecs_api_handles_tags():
module.fail_json(msg=missing_required_lib("botocore >= 1.12.46", reason="to use tags"))
if not service_mgr.ecs_task_long_format_enabled():
module.fail_json(msg="Cannot set task tags: long format task arns are required to set tags")
existing = service_mgr.list_tasks(module.params['cluster'], task_to_list, status_type)
results = dict(changed=False)
@ -374,7 +410,9 @@ def main():
module.params['overrides'],
module.params['count'],
module.params['started_by'],
module.params['launch_type'])
module.params['launch_type'],
module.params['tags'],
)
results['changed'] = True
elif module.params['operation'] == 'start':
@ -388,7 +426,8 @@ def main():
module.params['task_definition'],
module.params['overrides'],
module.params['container_instances'],
module.params['started_by']
module.params['started_by'],
module.params['tags'],
)
results['changed'] = True

View file

@ -623,7 +623,7 @@
- name: check that facts contain network configuration
assert:
that:
- "'networkConfiguration' in ecs_service_info.ansible_facts.services[0]"
- "'networkConfiguration' in ecs_service_info.services[0]"
- name: attempt to get facts from missing task definition
ecs_taskdefinition_info:
@ -738,6 +738,11 @@
<<: *aws_connection_info
register: ecs_fargate_service_network_with_awsvpc
- name: assert that public IP assignment is enabled
assert:
that:
- 'ecs_fargate_service_network_with_awsvpc.service.networkConfiguration.awsvpcConfiguration.assignPublicIp == "ENABLED"'
- name: create fargate ECS task with run task
ecs_task:
operation: run
@ -754,10 +759,67 @@
<<: *aws_connection_info
register: fargate_run_task_output
- name: assert that public IP assignment is enabled
assert:
that:
- 'ecs_fargate_service_network_with_awsvpc.service.networkConfiguration.awsvpcConfiguration.assignPublicIp == "ENABLED"'
# aws cli not installed in docker container; make sure it's installed.
- name: install awscli
pip:
state: present
name: awscli
- name: disable taskLongArnFormat
command: aws ecs put-account-setting --name taskLongArnFormat --value disabled
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
AWS_SESSION_TOKEN: "{{ security_token | default('') }}"
AWS_DEFAULT_REGION: "{{ aws_region }}"
- name: create fargate ECS task with run task and tags (LF disabled) (should fail)
ecs_task:
operation: run
cluster: "{{ ecs_cluster_name }}"
task_definition: "{{ ecs_task_name }}-vpc"
launch_type: FARGATE
count: 1
tags:
tag_key: tag_value
tag_key2: tag_value2
network_configuration:
subnets: "{{ setup_subnet.results | json_query('[].subnet.id') }}"
security_groups:
- '{{ setup_sg.group_id }}'
assign_public_ip: true
started_by: ansible_user
<<: *aws_connection_info
register: fargate_run_task_output_with_tags_fail
ignore_errors: yes
- name: enable taskLongArnFormat
command: aws ecs put-account-setting --name taskLongArnFormat --value enabled
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
AWS_SESSION_TOKEN: "{{ security_token | default('') }}"
AWS_DEFAULT_REGION: "{{ aws_region }}"
- name: create fargate ECS task with run task and tags
ecs_task:
operation: run
cluster: "{{ ecs_cluster_name }}"
task_definition: "{{ ecs_task_name }}-vpc"
launch_type: FARGATE
count: 1
tags:
tag_key: tag_value
tag_key2: tag_value2
network_configuration:
subnets: "{{ setup_subnet.results | json_query('[].subnet.id') }}"
security_groups:
- '{{ setup_sg.group_id }}'
assign_public_ip: true
started_by: ansible_user
<<: *aws_connection_info
register: fargate_run_task_output_with_tags
# ============================================================
# End tests for Fargate
@ -795,12 +857,12 @@
state: present
name: "{{ ecs_service_name }}"
cluster: "{{ ecs_cluster_name }}"
task_definition: "{{ ecs_service_info.ansible_facts.services[0].taskDefinition }}"
task_definition: "{{ ecs_service_info.services[0].taskDefinition }}"
desired_count: 0
deployment_configuration: "{{ ecs_service_deployment_configuration }}"
placement_strategy: "{{ ecs_service_placement_strategy }}"
load_balancers:
- targetGroupArn: "{{ ecs_service_info.ansible_facts.services[0].loadBalancers[0].targetGroupArn }}"
- targetGroupArn: "{{ ecs_service_info.services[0].loadBalancers[0].targetGroupArn }}"
containerName: "{{ ecs_task_name }}"
containerPort: "{{ ecs_task_container_port }}"
<<: *aws_connection_info
@ -821,12 +883,12 @@
state: present
name: "{{ ecs_service_name }}2"
cluster: "{{ ecs_cluster_name }}"
task_definition: "{{ ecs_service_info.ansible_facts.services[0].taskDefinition }}"
task_definition: "{{ ecs_service_info.services[0].taskDefinition }}"
desired_count: 0
deployment_configuration: "{{ ecs_service_deployment_configuration }}"
placement_strategy: "{{ ecs_service_placement_strategy }}"
load_balancers:
- targetGroupArn: "{{ ecs_service_info.ansible_facts.services[0].loadBalancers[0].targetGroupArn }}"
- targetGroupArn: "{{ ecs_service_info.services[0].loadBalancers[0].targetGroupArn }}"
containerName: "{{ ecs_task_name }}"
containerPort: "{{ ecs_task_container_port }}"
<<: *aws_connection_info
@ -904,6 +966,14 @@
<<: *aws_connection_info
ignore_errors: yes
- name: stop Fargate ECS task
ecs_task:
task: "{{ fargate_run_task_output_with_tags.task[0].taskArn }}"
task_definition: "{{ ecs_task_name }}-vpc"
operation: stop
cluster: "{{ ecs_cluster_name }}"
<<: *aws_connection_info
ignore_errors: yes
- name: pause to allow services to scale down
pause:
seconds: 60

View file

@ -25,6 +25,28 @@
<<: *aws_connection_info
register: ecs_taskdefinition_creation
# even after deleting the cluster and recreating with a different name
# the previous service can prevent the current service from starting
# while it's in a draining state. Check the service info and sleep
# if the service does not report as inactive.
- name: check if service is still running from a previous task
ecs_service_info:
service: "{{ resource_prefix }}"
cluster: "{{ resource_prefix }}"
details: yes
<<: *aws_connection_info
register: ecs_service_info_results
- name: delay if the service was not inactive
debug: var=ecs_service_info_results
- name: delay if the service was not inactive
pause:
seconds: 30
when:
- ecs_service_info_results.services|length >0
- ecs_service_info_results.services[0]['status'] != 'INACTIVE'
- name: create ecs_service
ecs_service:
name: "{{ resource_prefix }}"

View file

@ -25,6 +25,28 @@
<<: *aws_connection_info
register: ecs_taskdefinition_creation
# even after deleting the cluster and recreating with a different name
# the previous service can prevent the current service from starting
# while it's in a draining state. Check the service info and sleep
# if the service does not report as inactive.
- name: check if service is still running from a previous task
ecs_service_info:
service: "{{ resource_prefix }}"
cluster: "{{ resource_prefix }}"
details: yes
<<: *aws_connection_info
register: ecs_service_info_results
- name: delay if the service was not inactive
debug: var=ecs_service_info_results
- name: delay if the service was not inactive
pause:
seconds: 30
when:
- ecs_service_info_results.services|length >0
- ecs_service_info_results.services[0]['status'] != 'INACTIVE'
- name: create ecs_service
ecs_service:
name: "{{ resource_prefix }}"