Skip to content

Commit

Permalink
Support new enableExecuteCommand options for ECS service (#488)
Browse files Browse the repository at this point in the history
Support new enableExecuteCommand options for ECS service

SUMMARY

Support new ecs exec feature for ECS service

ISSUE TYPE

Feature Pull Request

COMPONENT NAME
ecs_service
ADDITIONAL INFORMATION



Create ECS service with enable_execute_command option,
- name: create exec service
  ecs_service:
    state: present
    ...
    enable_execute_command: true

and we can exec ECS task
$ aws ecs execute-command --cluster xxxxx --task arn:aws:ecs:us-east-1:*****:task/webapp/***** --container xxxxx --interactive --command /bin/bash


The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


Starting session with SessionId: ecs-execute-command-0c17f94b36227381f
root@ip-10-0-66-68:/#

Reviewed-by: Mark Chappell
Reviewed-by: Alina Buzachis
  • Loading branch information
Surgo authored Mar 20, 2023
1 parent 7829c60 commit 3ad3b97
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 66 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/488-ecs_service-support_exec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +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).
156 changes: 106 additions & 50 deletions plugins/modules/ecs_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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'):
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -875,11 +920,14 @@ 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

response = self.ecs.update_service(**params)

return self.jsonize(response['service'])

def jsonize(self, service):
Expand Down Expand Up @@ -967,8 +1015,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,
Expand Down Expand Up @@ -1081,47 +1130,54 @@ 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'],
)
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:
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'],
)
except botocore.exceptions.ClientError as e:
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.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, msg="Couldn't create service")

if response.get('tags', None):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
55 changes: 39 additions & 16 deletions tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }}"
Expand Down

0 comments on commit 3ad3b97

Please sign in to comment.