From f7830fb9a5655a0b470935437f5bb46593063eec Mon Sep 17 00:00:00 2001 From: Kosei Kitahara Date: Mon, 22 Mar 2021 00:24:29 +0900 Subject: [PATCH 1/2] Support new enableExecuteCommand options for ECS service --- .../488-ecs_service-support_exec.yml | 2 + plugins/modules/ecs_service.py | 148 ++++++++++++------ .../tasks/01_create_requirements.yml | 13 ++ .../ecs_cluster/tasks/20_ecs_service.yml | 55 +++++-- 4 files changed, 154 insertions(+), 64 deletions(-) create mode 100644 changelogs/fragments/488-ecs_service-support_exec.yml diff --git a/changelogs/fragments/488-ecs_service-support_exec.yml b/changelogs/fragments/488-ecs_service-support_exec.yml new file mode 100644 index 00000000000..02b76b03b62 --- /dev/null +++ b/changelogs/fragments/488-ecs_service-support_exec.yml @@ -0,0 +1,2 @@ +minor_changes: +- ecs_service - added new parameter ``enable_execute_command`` (https://github.com/ansible-collections/community.aws/pull/488). diff --git a/plugins/modules/ecs_service.py b/plugins/modules/ecs_service.py index 074dec4b176..d8890b9524a 100644 --- a/plugins/modules/ecs_service.py +++ b/plugins/modules/ecs_service.py @@ -131,6 +131,13 @@ rollback: type: bool description: If enabled, ECS will roll back your service to the last completed deployment after a failure. + enable_execute_command: + description: + - Whether or not to enable the execute command functionality for the containers in the ECS task. + - If I(enable_execute_command=true) execute command functionality is enabled on all containers in the ECS task. + required: false + type: bool + version_added: 5.4.0 placement_constraints: description: - The placement constraints for the tasks in the service. @@ -778,6 +785,9 @@ def is_matching_service(self, expected, existing): if boto3_tag_list_to_ansible_dict(existing.get('tags', [])) != (expected['tags'] or {}): return False + if (expected["enable_execute_command"] or False) != existing.get("enableExecuteCommand", False): + return False + # expected is params. DAEMON scheduling strategy returns desired count equal to # number of instances running; don't check desired count if scheduling strat is daemon if (expected['scheduling_strategy'] != 'DAEMON'): @@ -786,11 +796,30 @@ def is_matching_service(self, expected, existing): return True - def create_service(self, service_name, cluster_name, task_definition, load_balancers, - desired_count, client_token, role, deployment_controller, deployment_configuration, - placement_constraints, placement_strategy, health_check_grace_period_seconds, - network_configuration, service_registries, launch_type, platform_version, - scheduling_strategy, capacity_provider_strategy, tags, propagate_tags): + def create_service( + self, + service_name, + cluster_name, + task_definition, + load_balancers, + desired_count, + client_token, + role, + deployment_controller, + deployment_configuration, + placement_constraints, + placement_strategy, + health_check_grace_period_seconds, + network_configuration, + service_registries, + launch_type, + platform_version, + scheduling_strategy, + capacity_provider_strategy, + tags, + propagate_tags, + enable_execute_command, + ): params = dict( cluster=cluster_name, @@ -836,14 +865,30 @@ def create_service(self, service_name, cluster_name, task_definition, load_balan if scheduling_strategy: params['schedulingStrategy'] = scheduling_strategy + if enable_execute_command: + params["enableExecuteCommand"] = enable_execute_command + response = self.ecs.create_service(**params) return self.jsonize(response['service']) - def update_service(self, service_name, cluster_name, task_definition, desired_count, - deployment_configuration, placement_constraints, placement_strategy, - network_configuration, health_check_grace_period_seconds, - force_new_deployment, capacity_provider_strategy, load_balancers, - purge_placement_constraints, purge_placement_strategy): + def update_service( + self, + service_name, + cluster_name, + task_definition, + desired_count, + deployment_configuration, + placement_constraints, + placement_strategy, + network_configuration, + health_check_grace_period_seconds, + force_new_deployment, + capacity_provider_strategy, + load_balancers, + purge_placement_constraints, + purge_placement_strategy, + enable_execute_command, + ): params = dict( cluster=cluster_name, service=service_name, @@ -875,6 +920,8 @@ def update_service(self, service_name, cluster_name, task_definition, desired_co # desired count is not required if scheduling strategy is daemon if desired_count is not None: params['desiredCount'] = desired_count + if enable_execute_command is not None: + params["enableExecuteCommand"] = enable_execute_command if load_balancers: params['loadBalancers'] = load_balancers @@ -967,8 +1014,9 @@ def main(): base=dict(type='int') ) ), - propagate_tags=dict(required=False, choices=['TASK_DEFINITION', 'SERVICE']), - tags=dict(required=False, type='dict'), + propagate_tags=dict(required=False, choices=["TASK_DEFINITION", "SERVICE"]), + tags=dict(required=False, type="dict"), + enable_execute_command=dict(required=False, type="bool"), ) module = AnsibleAWSModule(argument_spec=argument_spec, @@ -1082,45 +1130,49 @@ def main(): task_definition = existing['taskDefinition'] # update required - response = service_mgr.update_service(module.params['name'], - module.params['cluster'], - task_definition, - module.params['desired_count'], - deploymentConfiguration, - module.params['placement_constraints'], - module.params['placement_strategy'], - network_configuration, - module.params['health_check_grace_period_seconds'], - module.params['force_new_deployment'], - capacityProviders, - updatedLoadBalancers, - module.params['purge_placement_constraints'], - module.params['purge_placement_strategy'], - ) + response = service_mgr.update_service( + module.params["name"], + module.params["cluster"], + task_definition, + module.params["desired_count"], + deploymentConfiguration, + module.params["placement_constraints"], + module.params["placement_strategy"], + network_configuration, + module.params["health_check_grace_period_seconds"], + module.params["force_new_deployment"], + capacityProviders, + updatedLoadBalancers, + module.params["purge_placement_constraints"], + module.params["purge_placement_strategy"], + module.params["enable_execute_command"], + ) else: try: - response = service_mgr.create_service(module.params['name'], - module.params['cluster'], - module.params['task_definition'], - loadBalancers, - module.params['desired_count'], - clientToken, - role, - deploymentController, - deploymentConfiguration, - module.params['placement_constraints'], - module.params['placement_strategy'], - module.params['health_check_grace_period_seconds'], - network_configuration, - serviceRegistries, - module.params['launch_type'], - module.params['platform_version'], - module.params['scheduling_strategy'], - capacityProviders, - module.params['tags'], - module.params['propagate_tags'], - ) + response = service_mgr.create_service( + module.params["name"], + module.params["cluster"], + module.params["task_definition"], + loadBalancers, + module.params["desired_count"], + clientToken, + role, + deploymentController, + deploymentConfiguration, + module.params["placement_constraints"], + module.params["placement_strategy"], + module.params["health_check_grace_period_seconds"], + network_configuration, + serviceRegistries, + module.params["launch_type"], + module.params["platform_version"], + module.params["scheduling_strategy"], + capacityProviders, + module.params["tags"], + module.params["propagate_tags"], + module.params["enable_execute_command"], + ) except botocore.exceptions.ClientError as e: module.fail_json_aws(e, msg="Couldn't create service") diff --git a/tests/integration/targets/ecs_cluster/tasks/01_create_requirements.yml b/tests/integration/targets/ecs_cluster/tasks/01_create_requirements.yml index ea2709a617d..31ca3cf275e 100644 --- a/tests/integration/targets/ecs_cluster/tasks/01_create_requirements.yml +++ b/tests/integration/targets/ecs_cluster/tasks/01_create_requirements.yml @@ -7,6 +7,19 @@ managed_policy: - AmazonEC2ContainerServiceRole wait: True + register: iam_role_creation + +- name: ensure AmazonECSTaskExecutionRolePolicy exists + iam_role: + name: "{{ ecs_task_role_name }}" + assume_role_policy_document: "{{ lookup('file','ecs-trust-policy.json') }}" + description: "Allows ECS containers to make calls to ECR" + state: present + create_instance_profile: false + managed_policy: + - AmazonECSTaskExecutionRolePolicy + wait: True + register: iam_execution_role - name: ensure AWSServiceRoleForECS role exists iam_role_info: diff --git a/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml b/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml index 40ea671fd4a..7b68d3a1707 100644 --- a/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml +++ b/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml @@ -204,6 +204,44 @@ - not ecs_service_scale_down.changed - ecs_service_scale_down.service.desiredCount == 0 +- name: update task definition + ecs_taskdefinition: + containers: "{{ ecs_task_containers }}" + family: "{{ ecs_task_name }}" + task_role_arn: "{{ ecs_task_role_name }}" + state: present + register: ecs_task_update + +- name: check that initial task definition changes + assert: + that: + - ecs_task_update.changed + +- name: Enable ExecuteCommand + ecs_service: + state: present + name: "{{ ecs_service_name }}" + cluster: "{{ ecs_cluster_name }}" + task_definition: "{{ ecs_task_name }}:{{ ecs_task_update.taskdefinition.revision }}" + desired_count: 0 + deployment_configuration: "{{ ecs_service_deployment_configuration }}" + placement_strategy: "{{ ecs_service_placement_strategy }}" + placement_constraints: + - type: distinctInstance + health_check_grace_period_seconds: "{{ ecs_service_health_check_grace_period }}" + load_balancers: + - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}" + containerName: "{{ ecs_task_name }}" + containerPort: "{{ ecs_task_container_port }}" + role: "{{ ecs_service_role_name }}" + enable_execute_command: True + register: ecs_service_execute + +- name: check that ECS service changed + assert: + that: + - ecs_service_execute.changed + - name: delete ECS service definition ecs_service: state: absent @@ -650,25 +688,10 @@ that: - ecs_service_remove_strategy.changed - "ecs_service_remove_strategy.service.placementStrategy | length == 0" + # ============================================================ # Begin tests for Fargate -- name: ensure AmazonECSTaskExecutionRolePolicy exists - iam_role: - name: "{{ ecs_task_role_name }}" - assume_role_policy_document: "{{ lookup('file','ecs-trust-policy.json') }}" - description: "Allows ECS containers to make calls to ECR" - state: present - create_instance_profile: false - managed_policy: - - AmazonECSTaskExecutionRolePolicy - wait: True - register: iam_execution_role - -- name: pause for iam availability - ansible.builtin.pause: - seconds: 20 - - name: create Fargate VPC-networked task definition with host port set to 8080 and unsupported network mode (expected to fail) ecs_taskdefinition: containers: "{{ ecs_fargate_task_containers }}" From a1d0b79fe8f882d4f6b5baee5135615006978045 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Tue, 14 Mar 2023 17:49:20 +0100 Subject: [PATCH 2/2] Make sure update is wrapped in a try --- .../488-ecs_service-support_exec.yml | 1 + plugins/modules/ecs_service.py | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/changelogs/fragments/488-ecs_service-support_exec.yml b/changelogs/fragments/488-ecs_service-support_exec.yml index 02b76b03b62..70bc3256d4d 100644 --- a/changelogs/fragments/488-ecs_service-support_exec.yml +++ b/changelogs/fragments/488-ecs_service-support_exec.yml @@ -1,2 +1,3 @@ minor_changes: - ecs_service - added new parameter ``enable_execute_command`` (https://github.com/ansible-collections/community.aws/pull/488). +- ecs_service - handle SDK errors more cleanly on update failures (https://github.com/ansible-collections/community.aws/pull/488). diff --git a/plugins/modules/ecs_service.py b/plugins/modules/ecs_service.py index d8890b9524a..2009dc3b54a 100644 --- a/plugins/modules/ecs_service.py +++ b/plugins/modules/ecs_service.py @@ -927,6 +927,7 @@ def update_service( params['loadBalancers'] = load_balancers response = self.ecs.update_service(**params) + return self.jsonize(response['service']) def jsonize(self, service): @@ -1129,24 +1130,27 @@ def main(): if task_definition is None and module.params['force_new_deployment']: task_definition = existing['taskDefinition'] - # update required - response = service_mgr.update_service( - module.params["name"], - module.params["cluster"], - task_definition, - module.params["desired_count"], - deploymentConfiguration, - module.params["placement_constraints"], - module.params["placement_strategy"], - network_configuration, - module.params["health_check_grace_period_seconds"], - module.params["force_new_deployment"], - capacityProviders, - updatedLoadBalancers, - module.params["purge_placement_constraints"], - module.params["purge_placement_strategy"], - module.params["enable_execute_command"], - ) + try: + # update required + response = service_mgr.update_service( + module.params["name"], + module.params["cluster"], + task_definition, + module.params["desired_count"], + deploymentConfiguration, + module.params["placement_constraints"], + module.params["placement_strategy"], + network_configuration, + module.params["health_check_grace_period_seconds"], + module.params["force_new_deployment"], + capacityProviders, + updatedLoadBalancers, + module.params["purge_placement_constraints"], + module.params["purge_placement_strategy"], + module.params["enable_execute_command"], + ) + except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: + module.fail_json_aws(e, msg="Couldn't create service") else: try: @@ -1173,7 +1177,7 @@ def main(): module.params["propagate_tags"], module.params["enable_execute_command"], ) - except botocore.exceptions.ClientError as e: + except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Couldn't create service") if response.get('tags', None):