diff --git a/changelogs/fragments/574-ecs_taskdefinition-improvement.yml b/changelogs/fragments/574-ecs_taskdefinition-improvement.yml new file mode 100644 index 00000000000..c8667401464 --- /dev/null +++ b/changelogs/fragments/574-ecs_taskdefinition-improvement.yml @@ -0,0 +1,3 @@ +bugfixes: +- ecs_taskdefinition - ensure cast to integer (https://github.com/ansible-collections/community.aws/pull/574). +- ecs_taskdefinition - fix idempotency (https://github.com/ansible-collections/community.aws/pull/574). diff --git a/plugins/modules/ecs_taskdefinition.py b/plugins/modules/ecs_taskdefinition.py index 6696e92acb3..5f144fd4763 100644 --- a/plugins/modules/ecs_taskdefinition.py +++ b/plugins/modules/ecs_taskdefinition.py @@ -13,7 +13,10 @@ short_description: register a task definition in ecs description: - Registers or deregisters task definitions in the Amazon Web Services (AWS) EC2 Container Service (ECS). -author: Mark Chance (@Java1Guy) +author: + - Mark Chance (@Java1Guy) + - Alina Buzachis (@alinabuzachis) +requirements: [ json, botocore, boto3 ] options: state: description: @@ -189,7 +192,7 @@ linuxParameters: description: Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. required: False - type: list + type: dict suboptions: capabilities: description: @@ -410,6 +413,8 @@ description: The type of the ulimit. type: str required: False + choices: ['core', 'cpu', 'data', 'fsize', 'locks', 'memlock', 'msgqueue', 'nice', 'nofile', 'nproc', 'rss', + 'rtprio', 'rttime', 'sigpending', 'stack'] softLimit: description: The soft limit for the ulimit type. type: int @@ -642,9 +647,9 @@ except ImportError: pass # caught by AnsibleAWSModule -from ansible.module_utils._text import to_text from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry class EcsTaskManager: @@ -653,13 +658,13 @@ class EcsTaskManager: def __init__(self, module): self.module = module - self.ecs = module.client('ecs') + self.ecs = module.client('ecs', AWSRetry.jittered_backoff()) def describe_task(self, task_name): try: - response = self.ecs.describe_task_definition(taskDefinition=task_name) + response = self.ecs.describe_task_definition(aws_retry=True, taskDefinition=task_name) return response['taskDefinition'] - except botocore.exceptions.ClientError: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: return None def register_task(self, family, task_role_arn, execution_role_arn, network_mode, container_definitions, volumes, launch_type, cpu, memory): @@ -667,7 +672,7 @@ def register_task(self, family, task_role_arn, execution_role_arn, network_mode, # Ensures the number parameters are int as required by boto for container in container_definitions: - for param in ('memory', 'cpu', 'memoryReservation'): + for param in ('memory', 'cpu', 'memoryReservation', 'startTimeout', 'stopTimeout'): if param in container: container[param] = int(container[param]) @@ -681,6 +686,23 @@ def register_task(self, family, task_role_arn, execution_role_arn, network_mode, self.module.fail_json(msg="In awsvpc network mode, host port must be set to the same as " "container port or not be set") + if 'linuxParameters' in container: + for linux_param in container.get('linuxParameters'): + if linux_param == 'tmpfs': + for tmpfs_param in container['linuxParameters']['tmpfs']: + if 'size' in tmpfs_param: + tmpfs_param['size'] = int(tmpfs_param['size']) + + for param in ('maxSwap', 'swappiness', 'sharedMemorySize'): + if param in linux_param: + container['linuxParameters'][param] = int(container['linuxParameters'][param]) + + if 'ulimits' in container: + for limits_mapping in container['ulimits']: + for limit in ('softLimit', 'hardLimit'): + if limit in limits_mapping: + limits_mapping[limit] = int(limits_mapping[limit]) + validated_containers.append(container) params = dict( @@ -701,8 +723,8 @@ def register_task(self, family, task_role_arn, execution_role_arn, network_mode, params['executionRoleArn'] = execution_role_arn try: - response = self.ecs.register_task_definition(**params) - except botocore.exceptions.ClientError as e: + response = self.ecs.register_task_definition(aws_retry=True, **params) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self.module.fail_json_aws(e, msg="Failed to register task") return response['taskDefinition'] @@ -795,28 +817,31 @@ def main(): module.fail_json(msg='links parameter is not supported if network mode is awsvpc.') for environment in container.get('environment', []): - environment['value'] = to_text(environment['value']) + environment['value'] = environment['value'] for environment_file in container.get('environmentFiles', []): if environment_file['type'] != 's3': module.fail_json(msg='The only supported value for environmentFiles is s3.') for linux_param in container.get('linuxParameters', {}): - if linux_param.get('devices') and launch_type == 'FARGATE': + if linux_param == 'maxSwap' and launch_type == 'FARGATE': module.fail_json(msg='devices parameter is not supported with the FARGATE launch type.') - if linux_param.get('maxSwap') and launch_type == 'FARGATE': + if linux_param == 'maxSwap' and launch_type == 'FARGATE': module.fail_json(msg='maxSwap parameter is not supported with the FARGATE launch type.') - elif linux_param.get('maxSwap') and linux_param['maxSwap'] < 0: + elif linux_param == 'maxSwap' and int(container['linuxParameters']['maxSwap']) < 0: module.fail_json(msg='Accepted values for maxSwap are 0 or any positive integer.') - if linux_param.get('swappiness') and (linux_param['swappiness'] < 0 or linux_param['swappiness'] > 100): + if ( + linux_param == 'swappiness' and + (int(container['linuxParameters']['swappiness']) < 0 or int(container['linuxParameters']['swappiness']) > 100) + ): module.fail_json(msg='Accepted values for swappiness are whole numbers between 0 and 100.') - if linux_param.get('sharedMemorySize') and launch_type == 'FARGATE': + if linux_param == 'sharedMemorySize' and launch_type == 'FARGATE': module.fail_json(msg='sharedMemorySize parameter is not supported with the FARGATE launch type.') - if linux_param.get('tmpfs') and launch_type == 'FARGATE': + if linux_param == 'tmpfs' and launch_type == 'FARGATE': module.fail_json(msg='tmpfs parameter is not supported with the FARGATE launch type.') if container.get('hostname') and network_mode == 'awsvpc': @@ -862,14 +887,24 @@ def _right_has_values_of_left(left, right): for list_val in left_list: if list_val not in right_list: - return False + # if list_val is the port mapping, the key 'protocol' may be absent (but defaults to 'tcp') + # fill in that default if absent and see if it is in right_list then + if isinstance(list_val, dict) and not list_val.get('protocol'): + modified_list_val = dict(list_val) + modified_list_val.update(protocol='tcp') + if modified_list_val in right_list: + continue else: return False # Make sure right doesn't have anything that left doesn't for k, v in right.items(): if v and k not in left: - return False + # 'essential' defaults to True when not specified + if k == 'essential' and v is True: + pass + else: + return False return True diff --git a/tests/integration/targets/ecs_cluster/defaults/main.yml b/tests/integration/targets/ecs_cluster/defaults/main.yml index 20e010e366d..0a34cf0232e 100644 --- a/tests/integration/targets/ecs_cluster/defaults/main.yml +++ b/tests/integration/targets/ecs_cluster/defaults/main.yml @@ -7,6 +7,8 @@ ecs_service_name: "{{ resource_prefix }}-service" ecs_task_image_path: nginx ecs_task_name: "{{ resource_prefix }}-task" ecs_task_memory: 128 +target_swap_mb: 0 +target_swappiness: 80 ecs_task_containers: - name: "{{ ecs_task_name }}" image: "{{ ecs_task_image_path }}" @@ -15,6 +17,9 @@ ecs_task_containers: portMappings: - containerPort: "{{ ecs_task_container_port }}" hostPort: "{{ ecs_task_host_port|default(0) }}" + linuxParameters: + maxSwap: "{{ target_swap_mb }}" + swappiness: "{{ target_swappiness }}" mountPoints: "{{ ecs_task_mount_points|default([]) }}" ecs_service_deployment_configuration: minimum_healthy_percent: 0 diff --git a/tests/integration/targets/ecs_cluster/tasks/full_test.yml b/tests/integration/targets/ecs_cluster/tasks/full_test.yml index a463fa5de0d..fbf50cf05db 100644 --- a/tests/integration/targets/ecs_cluster/tasks/full_test.yml +++ b/tests/integration/targets/ecs_cluster/tasks/full_test.yml @@ -72,7 +72,7 @@ - name: create subnets ec2_vpc_subnet: - az: '{{ ec2_region }}{{ item.zone }}' + az: '{{ aws_region }}{{ item.zone }}' tags: Name: '{{ resource_prefix }}_ecs_cluster-subnet-{{ item.zone }}' vpc_id: '{{ setup_vpc.vpc.id }}' @@ -116,7 +116,7 @@ - name: provision ec2 instance to create an image ec2_instance: key_name: '{{ ec2_keypair|default(setup_key.key.name) }}' - instance_type: t2.micro + instance_type: t3.micro state: present image_id: '{{ ecs_image_id }}' wait: yes @@ -156,7 +156,7 @@ state: present scheme: internal security_groups: '{{ setup_sg.group_id }}' - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" listeners: - Protocol: HTTP Port: 80 @@ -187,8 +187,6 @@ assert: that: - not ecs_task_definition_again.changed - # FIXME: task definition should not change, will need #26752 or equivalent - ignore_errors: yes - name: obtain ECS task definition facts ecs_taskdefinition_info: @@ -281,7 +279,7 @@ containerName: "{{ ecs_task_name }}" containerPort: "{{ ecs_task_container_port }}" network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - '{{ setup_sg.group_id }}' register: ecs_service_network_without_awsvpc_task @@ -389,7 +387,7 @@ containerName: "{{ ecs_task_name }}" containerPort: "{{ ecs_task_container_port }}" network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - '{{ setup_sg.group_id }}' register: create_ecs_service_with_vpc @@ -423,7 +421,7 @@ containerName: "{{ ecs_task_name }}" containerPort: "{{ ecs_task_container_port }}" network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - "{{ resource_prefix }}-ecs-vpc-test-sg" register: update_ecs_service_with_vpc @@ -674,7 +672,7 @@ deployment_configuration: "{{ ecs_service_deployment_configuration }}" launch_type: FARGATE network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - '{{ setup_sg.group_id }}' assign_public_ip: true @@ -693,7 +691,7 @@ launch_type: FARGATE count: 1 network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - '{{ setup_sg.group_id }}' assign_public_ip: true @@ -725,7 +723,7 @@ tag_key: tag_value tag_key2: tag_value2 network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - '{{ setup_sg.group_id }}' assign_public_ip: true @@ -752,7 +750,7 @@ tag_key: tag_value tag_key2: tag_value2 network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - '{{ setup_sg.group_id }}' assign_public_ip: true @@ -767,7 +765,7 @@ launch_type: FARGATE count: 1 network_configuration: - subnets: "{{ setup_subnet.results | community.general.json_query('[].subnet.id') }}" + subnets: "{{ setup_subnet.results | map(attribute='subnet.id') | list }}" security_groups: - '{{ setup_sg.group_id }}' assign_public_ip: false @@ -857,8 +855,6 @@ ignore_errors: yes register: ecs_service_scale_down - - - name: scale down scheduling_strategy service ecs_service: name: "{{ ecs_service_name }}-replica"