Skip to content

Commit

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

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.