Repository URL to install this package:
Version:
6.0.0 ▾
|
#!/usr/bin/python
# This file is part of Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: ecs_taskdefinition
version_added: 1.0.0
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)
- Alina Buzachis (@alinabuzachis)
options:
state:
description:
- State whether the task definition should exist or be deleted.
required: true
choices: ['present', 'absent']
type: str
arn:
description:
- The ARN of the task description to delete.
required: false
type: str
family:
description:
- A Name that would be given to the task definition.
required: false
type: str
revision:
description:
- A revision number for the task definition.
required: False
type: int
force_create:
description:
- Always create new task definition.
required: False
type: bool
default: false
containers:
description:
- A list of containers definitions.
- See U(https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecs.html) for a complete list of parameters.
required: True
type: list
elements: dict
suboptions:
name:
description: The name of a container.
required: False
type: str
image:
description: The image used to start a container.
required: False
type: str
repositoryCredentials:
description: The private repository authentication credentials to use.
required: False
type: dict
suboptions:
credentialsParameter:
description:
- The Amazon Resource Name (ARN) of the secret containing the private repository credentials.
required: True
type: str
cpu:
description: The number of cpu units reserved for the container.
required: False
type: int
memory:
description: The amount (in MiB) of memory to present to the container.
required: False
type: int
memoryReservation:
description: The soft limit (in MiB) of memory to reserve for the container.
required: False
type: int
links:
description:
- Allows containers to communicate with each other without the need for port mappings.
- This parameter is only supported if I(network_mode=bridge).
required: False
type: list
portMappings:
description: The list of port mappings for the container.
required: False
type: list
elements: dict
suboptions:
containerPort:
description: The port number on the container that is bound to the user-specified or automatically assigned host port.
required: False
type: int
hostPort:
description: The port number on the container instance to reserve for your container.
required: False
type: int
protocol:
description: The protocol used for the port mapping.
required: False
type: str
default: tcp
choices: ['tcp', 'udp']
essential:
description:
- If I(essential=True), and the container fails or stops for any reason, all other containers that are part of the task are stopped.
required: False
type: bool
entryPoint:
description: The entry point that is passed to the container.
required: False
type: str
command:
description: The command that is passed to the container.
required: False
type: list
environment:
description: The environment variables to pass to a container.
required: False
type: list
elements: dict
suboptions:
name:
description: The name of the key-value pair.
required: False
type: str
value:
description: The value of the key-value pair.
required: False
type: str
environmentFiles:
description: A list of files containing the environment variables to pass to a container.
required: False
type: list
elements: dict
suboptions:
value:
description: The Amazon Resource Name (ARN) of the Amazon S3 object containing the environment variable file.
required: False
type: str
type:
description: The file type to use. The only supported value is C(s3).
required: False
type: str
mountPoints:
description: The mount points for data volumes in your container.
required: False
type: list
elements: dict
suboptions:
sourceVolume:
description: The name of the volume to mount.
required: False
type: str
containerPath:
description: The path on the container to mount the host volume at.
required: False
type: str
readOnly:
description:
- If this value is C(True), the container has read-only access to the volume.
- If this value is C(False), then the container can write to the volume.
required: False
default: False
type: bool
volumesFrom:
description: Data volumes to mount from another container.
required: False
type: list
elements: dict
suboptions:
sourceContainer:
description:
- The name of another container within the same task definition from which to mount volumes.
required: False
type: str
readOnly:
description:
- If this value is C(True), the container has read-only access to the volume.
- If this value is C(False), then the container can write to the volume.
required: False
default: False
type: bool
linuxParameters:
description: Linux-specific modifications that are applied to the container, such as Linux kernel capabilities.
required: False
type: dict
suboptions:
capabilities:
description:
- The Linux capabilities for the container that are added to or dropped from the default configuration provided by Docker.
required: False
type: dict
suboptions:
add:
description:
- The Linux capabilities for the container that have been added to the default configuration provided by Docker.
- If I(launch_type=FARGATE), this parameter is not supported.
required: False
type: list
choices: ["ALL", "AUDIT_CONTROL", "AUDIT_WRITE", "BLOCK_SUSPEND", "CHOWN", "DAC_OVERRIDE", "DAC_READ_SEARCH", "FOWNER",
"FSETID", "IPC_LOCK", "IPC_OWNER", "KILL", "LEASE", "LINUX_IMMUTABLE", "MAC_ADMIN", "MAC_OVERRIDE", "MKNOD",
"NET_ADMIN", "NET_BIND_SERVICE", "NET_BROADCAST", "NET_RAW", "SETFCAP", "SETGID", "SETPCAP", "SETUID",
"SYS_ADMIN", "SYS_BOOT", "SYS_CHROOT", "SYS_MODULE", "SYS_NICE", "SYS_PACCT", "SYS_PTRACE", "SYS_RAWIO",
"SYS_RESOURCE", "SYS_TIME", "SYS_TTY_CONFIG", "SYSLOG", "WAKE_ALARM"]
drop:
description:
- The Linux capabilities for the container that have been removed from the default configuration provided by Docker.
required: False
type: list
choices: ["ALL", "AUDIT_CONTROL", "AUDIT_WRITE", "BLOCK_SUSPEND", "CHOWN", "DAC_OVERRIDE", "DAC_READ_SEARCH", "FOWNER",
"FSETID", "IPC_LOCK", "IPC_OWNER", "KILL", "LEASE", "LINUX_IMMUTABLE", "MAC_ADMIN", "MAC_OVERRIDE", "MKNOD",
"NET_ADMIN", "NET_BIND_SERVICE", "NET_BROADCAST", "NET_RAW", "SETFCAP", "SETGID", "SETPCAP", "SETUID",
"SYS_ADMIN", "SYS_BOOT", "SYS_CHROOT", "SYS_MODULE", "SYS_NICE", "SYS_PACCT", "SYS_PTRACE", "SYS_RAWIO",
"SYS_RESOURCE", "SYS_TIME", "SYS_TTY_CONFIG", "SYSLOG", "WAKE_ALARM"]
devices:
description:
- Any host devices to expose to the container.
- If I(launch_type=FARGATE), this parameter is not supported.
required: False
type: list
elements: dict
suboptions:
hostPath:
description: The path for the device on the host container instance.
required: True
type: str
containerPath:
description: The path inside the container at which to expose the host device.
required: False
type: str
permissions:
description: The explicit permissions to provide to the container for the device.
required: False
type: list
initProcessEnabled:
description: Run an init process inside the container that forwards signals and reaps processes.
required: False
type: bool
sharedMemorySize:
description:
- The value for the size (in MiB) of the /dev/shm volume.
- If I(launch_type=FARGATE), this parameter is not supported.
required: False
type: int
tmpfs:
description:
- The container path, mount options, and size (in MiB) of the tmpfs mount.
- If I(launch_type=FARGATE), this parameter is not supported.
required: False
type: list
elements: dict
suboptions:
containerPath:
description: The absolute file path where the tmpfs volume is to be mounted.
required: True
type: str
size:
description: The size (in MiB) of the tmpfs volume.
required: True
type: int
mountOptions:
description: The list of tmpfs volume mount options.
required: False
type: list
choices: ["defaults", "ro", "rw", "suid", "nosuid", "dev", "nodev", "exec", "noexec", "sync", "async", "dirsync",
"remount", "mand", "nomand", "atime", "noatime", "diratime", "nodiratime", "bind", "rbind", "unbindable",
"runbindable", "private", "rprivate", "shared", "rshared", "slave", "rslave", "relatime", "norelatime",
"strictatime", "nostrictatime", "mode", "uid", "gid", "nr_inodes", "nr_blocks", "mpol"]
maxSwap:
description:
- The total amount of swap memory (in MiB) a container can use.
- If I(launch_type=FARGATE), this parameter is not supported.
required: False
type: int
swappiness:
description:
- This allows you to tune a container's memory swappiness behavior.
- If I(launch_type=FARGATE), this parameter is not supported.
required: False
type: int
secrets:
description: The secrets to pass to the container.
required: False
type: list
elements: dict
suboptions:
name:
description: The value to set as the environment variable on the container.
required: True
type: str
size:
description: The secret to expose to the container.
required: True
type: str
dependsOn:
description:
- The dependencies defined for container startup and shutdown.
- When a dependency is defined for container startup, for container shutdown it is reversed.
required: False
type: list
elements: dict
suboptions:
containerName:
description: The name of a container.
type: str
required: True
condition:
description: The dependency condition of the container.
type: str
required: True
choices: ["start", "complete", "success", "healthy"]
startTimeout:
description: Time duration (in seconds) to wait before giving up on resolving dependencies for a container.
required: False
type: int
stopTimeout:
description: Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own.
required: False
type: int
hostname:
description:
- The hostname to use for your container.
- This parameter is not supported if I(network_mode=awsvpc).
required: False
type: str
user:
description:
- The user to use inside the container.
- This parameter is not supported for Windows containers.
required: False
type: str
workingDirectory:
description: The working directory in which to run commands inside the container.
required: False
type: str
disableNetworking:
description: When this parameter is C(True), networking is disabled within the container.
required: False
type: bool
privileged:
description: When this parameter is C(True), the container is given elevated privileges on the host container instance.
required: False
type: bool
readonlyRootFilesystem:
description: When this parameter is C(True), the container is given read-only access to its root file system.
required: false
type: bool
dnsServers:
description:
- A list of DNS servers that are presented to the container.
- This parameter is not supported for Windows containers.
required: False
type: list
dnsSearchDomains:
description:
- A list of DNS search domains that are presented to the container.
- This parameter is not supported for Windows containers.
required: False
type: list
extraHosts:
description:
- A list of hostnames and IP address mappings to append to the /etc/hosts file on the container.
- This parameter is not supported for Windows containers or tasks that use I(network_mode=awsvpc).
required: False
type: list
elements: dict
suboptions:
hostname:
description: The hostname to use in the /etc/hosts entry.
type: str
required: False
ipAddress:
description: The IP address to use in the /etc/hosts entry.
type: str
required: False
dockerSecurityOptions:
description:
- A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems.
- This parameter is not supported for Windows containers.
required: False
type: list
interactive:
description:
- When I(interactive=True), it allows to deploy containerized applications that require stdin or a tty to be allocated.
required: False
type: bool
pseudoTerminal:
description: When this parameter is C(True), a TTY is allocated.
required: False
type: bool
dockerLabels:
description: A key/value map of labels to add to the container.
required: False
type: dict
ulimits:
description:
- A list of ulimits to set in the container.
- This parameter is not supported for Windows containers.
required: False
type: list
elements: dict
suboptions:
name:
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
required: False
hardLimit:
description: The hard limit for the ulimit type.
type: int
required: False
logConfiguration:
description: The log configuration specification for the container.
required: False
type: dict
suboptions:
logDriver:
description:
- The log driver to use for the container.
- For tasks on AWS Fargate, the supported log drivers are C(awslogs), C(splunk), and C(awsfirelens).
- For tasks hosted on Amazon EC2 instances, the supported log drivers are C(awslogs), C(fluentd),
C(gelf), C(json-file), C(journald), C(logentries), C(syslog), C(splunk), and C(awsfirelens).
type: str
required: False
options:
description: The configuration options to send to the log driver.
required: False
type: str
secretOptions:
description: The secrets to pass to the log configuration.
required: False
type: list
elements: dict
suboptions:
name:
description: The name of the secret.
type: str
required: False
valueFrom:
description: The secret to expose to the container.
type: str
required: False
healthCheck:
description: The health check command and associated configuration parameters for the container.
required: False
type: dict
systemControls:
description: A list of namespaced kernel parameters to set in the container.
required: False
type: list
resourceRequirements:
description:
- The type and amount of a resource to assign to a container.
- The only supported resource is a C(GPU).
required: False
type: list
network_mode:
description:
- The Docker networking mode to use for the containers in the task.
- Windows containers must use I(network_mode=default), which will utilize docker NAT networking.
- Setting I(network_mode=default) for a Linux container will use C(bridge) mode.
required: false
default: bridge
choices: [ 'default', 'bridge', 'host', 'none', 'awsvpc' ]
type: str
task_role_arn:
description:
- The Amazon Resource Name (ARN) of the IAM role that containers in this task can assume. All containers in this task are granted
the permissions that are specified in this role.
required: false
type: str
execution_role_arn:
description:
- The Amazon Resource Name (ARN) of the task execution role that the Amazon ECS container agent and the Docker daemon can assume.
required: false
type: str
volumes:
description:
- A list of names of volumes to be attached.
required: False
type: list
elements: dict
suboptions:
name:
type: str
description: The name of the volume.
required: true
launch_type:
description:
- The launch type on which to run your task.
required: false
type: str
choices: ["EC2", "FARGATE"]
cpu:
description:
- The number of cpu units used by the task. If I(launch_type=EC2), this field is optional and any value can be used.
- If I(launch_type=FARGATE), this field is required and you must use one of C(256), C(512), C(1024), C(2048), C(4096).
required: false
type: str
memory:
description:
- The amount (in MiB) of memory used by the task. If I(launch_type=EC2), this field is optional and any value can be used.
- If I(launch_type=FARGATE), this field is required and is limited by the CPU.
required: false
type: str
placement_constraints:
version_added: 2.1.0
description:
- Placement constraint objects to use for the task.
- You can specify a maximum of 10 constraints per task.
- Task placement constraints are not supported for tasks run on Fargate.
required: false
type: list
elements: dict
suboptions:
type:
description: The type of constraint.
type: str
expression:
description: A cluster query language expression to apply to the constraint.
type: str
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
'''
EXAMPLES = r'''
- name: Create task definition
community.aws.ecs_taskdefinition:
containers:
- name: simple-app
cpu: 10
essential: true
image: "httpd:2.4"
memory: 300
mountPoints:
- containerPath: /usr/local/apache2/htdocs
sourceVolume: my-vol
portMappings:
- containerPort: 80
hostPort: 80
logConfiguration:
logDriver: awslogs
options:
awslogs-group: /ecs/test-cluster-taskdef
awslogs-region: us-west-2
awslogs-stream-prefix: ecs
- name: busybox
command:
- >
/bin/sh -c "while true; do echo '<html><head><title>Amazon ECS Sample App</title></head><body><div><h1>Amazon ECS Sample App</h1><h2>Congratulations!
</h2><p>Your application is now running on a container in Amazon ECS.</p>' > top; /bin/date > date ; echo '</div></body></html>' > bottom;
cat top date bottom > /usr/local/apache2/htdocs/index.html ; sleep 1; done"
cpu: 10
entryPoint:
- sh
- "-c"
essential: false
image: busybox
memory: 200
volumesFrom:
- sourceContainer: simple-app
volumes:
- name: my-vol
family: test-cluster-taskdef
state: present
register: task_output
- name: Create task definition
community.aws.ecs_taskdefinition:
family: nginx
containers:
- name: nginx
essential: true
image: "nginx"
portMappings:
- containerPort: 8080
hostPort: 8080
cpu: 512
memory: 1024
state: present
- name: Create task definition
community.aws.ecs_taskdefinition:
family: nginx
containers:
- name: nginx
essential: true
image: "nginx"
portMappings:
- containerPort: 8080
hostPort: 8080
launch_type: FARGATE
cpu: 512
memory: 1024
state: present
network_mode: awsvpc
- name: Create task definition
community.aws.ecs_taskdefinition:
family: nginx
containers:
- name: nginx
essential: true
image: "nginx"
portMappings:
- containerPort: 8080
hostPort: 8080
cpu: 512
memory: 1024
dependsOn:
- containerName: "simple-app"
condition: "start"
# Create Task Definition with Environment Variables and Secrets
- name: Create task definition
community.aws.ecs_taskdefinition:
family: nginx
containers:
- name: nginx
essential: true
image: "nginx"
environment:
- name: "PORT"
value: "8080"
secrets:
# For variables stored in Secrets Manager
- name: "NGINX_HOST"
valueFrom: "arn:aws:secretsmanager:us-west-2:123456789012:secret:nginx/NGINX_HOST"
# For variables stored in Parameter Store
- name: "API_KEY"
valueFrom: "arn:aws:ssm:us-west-2:123456789012:parameter/nginx/API_KEY"
launch_type: FARGATE
cpu: 512
memory: 1GB
state: present
network_mode: awsvpc
'''
RETURN = r'''
taskdefinition:
description: a reflection of the input parameters
type: dict
returned: always
'''
try:
import botocore
except ImportError:
pass # caught by AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
class EcsTaskManager:
"""Handles ECS Tasks"""
def __init__(self, module):
self.module = module
self.ecs = module.client('ecs', AWSRetry.jittered_backoff())
def describe_task(self, task_name):
try:
response = self.ecs.describe_task_definition(aws_retry=True, taskDefinition=task_name)
return response['taskDefinition']
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, placement_constraints):
validated_containers = []
# Ensures the number parameters are int as required by boto
for container in container_definitions:
for param in ('memory', 'cpu', 'memoryReservation', 'startTimeout', 'stopTimeout'):
if param in container:
container[param] = int(container[param])
if 'portMappings' in container:
for port_mapping in container['portMappings']:
for port in ('hostPort', 'containerPort'):
if port in port_mapping:
port_mapping[port] = int(port_mapping[port])
if network_mode == 'awsvpc' and 'hostPort' in port_mapping:
if port_mapping['hostPort'] != port_mapping.get('containerPort'):
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(
family=family,
taskRoleArn=task_role_arn,
containerDefinitions=container_definitions,
volumes=volumes
)
if network_mode != 'default':
params['networkMode'] = network_mode
if cpu:
params['cpu'] = cpu
if memory:
params['memory'] = memory
if launch_type:
params['requiresCompatibilities'] = [launch_type]
if execution_role_arn:
params['executionRoleArn'] = execution_role_arn
if placement_constraints:
params['placementConstraints'] = placement_constraints
try:
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']
def describe_task_definitions(self, family):
data = {
"taskDefinitionArns": [],
"nextToken": None
}
def fetch():
# Boto3 is weird about params passed, so only pass nextToken if we have a value
params = {
'familyPrefix': family
}
if data['nextToken']:
params['nextToken'] = data['nextToken']
result = self.ecs.list_task_definitions(**params)
data['taskDefinitionArns'] += result['taskDefinitionArns']
data['nextToken'] = result.get('nextToken', None)
return data['nextToken'] is not None
# Fetch all the arns, possibly across multiple pages
while fetch():
pass
# Return the full descriptions of the task definitions, sorted ascending by revision
return list(
sorted(
[self.ecs.describe_task_definition(taskDefinition=arn)['taskDefinition'] for arn in data['taskDefinitionArns']],
key=lambda td: td['revision']
)
)
def deregister_task(self, taskArn):
response = self.ecs.deregister_task_definition(taskDefinition=taskArn)
return response['taskDefinition']
def main():
argument_spec = dict(
state=dict(required=True, choices=['present', 'absent']),
arn=dict(required=False, type='str'),
family=dict(required=False, type='str'),
revision=dict(required=False, type='int'),
force_create=dict(required=False, default=False, type='bool'),
containers=dict(required=True, type='list', elements='dict'),
network_mode=dict(required=False, default='bridge', choices=['default', 'bridge', 'host', 'none', 'awsvpc'], type='str'),
task_role_arn=dict(required=False, default='', type='str'),
execution_role_arn=dict(required=False, default='', type='str'),
volumes=dict(required=False, type='list', elements='dict'),
launch_type=dict(required=False, choices=['EC2', 'FARGATE']),
cpu=dict(),
memory=dict(required=False, type='str'),
placement_constraints=dict(required=False, type='list', elements='dict',
options=dict(type=dict(type='str'), expression=dict(type='str'))),
)
module = AnsibleAWSModule(argument_spec=argument_spec,
supports_check_mode=True,
required_if=[('launch_type', 'FARGATE', ['cpu', 'memory'])]
)
task_to_describe = None
task_mgr = EcsTaskManager(module)
results = dict(changed=False)
if module.params['state'] == 'present':
if 'containers' not in module.params or not module.params['containers']:
module.fail_json(msg="To use task definitions, a list of containers must be specified")
if 'family' not in module.params or not module.params['family']:
module.fail_json(msg="To use task definitions, a family must be specified")
network_mode = module.params['network_mode']
launch_type = module.params['launch_type']
placement_constraints = module.params['placement_constraints']
if launch_type == 'FARGATE':
if network_mode != 'awsvpc':
module.fail_json(msg="To use FARGATE launch type, network_mode must be awsvpc")
if placement_constraints:
module.fail_json(msg="Task placement constraints are not supported for tasks run on Fargate")
for container in module.params['containers']:
if container.get('links') and network_mode == 'awsvpc':
module.fail_json(msg='links parameter is not supported if network mode is awsvpc.')
for environment in container.get('environment', []):
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 == 'maxSwap' and launch_type == 'FARGATE':
module.fail_json(msg='devices parameter is not supported with the FARGATE launch type.')
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 == 'maxSwap' and int(container['linuxParameters']['maxSwap']) < 0:
module.fail_json(msg='Accepted values for maxSwap are 0 or any positive integer.')
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 == 'sharedMemorySize' and launch_type == 'FARGATE':
module.fail_json(msg='sharedMemorySize parameter is not supported with the FARGATE launch type.')
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':
module.fail_json(msg='hostname parameter is not supported when the awsvpc network mode is used.')
if container.get('extraHosts') and network_mode == 'awsvpc':
module.fail_json(msg='extraHosts parameter is not supported when the awsvpc network mode is used.')
family = module.params['family']
existing_definitions_in_family = task_mgr.describe_task_definitions(module.params['family'])
if 'revision' in module.params and module.params['revision']:
# The definition specifies revision. We must guarantee that an active revision of that number will result from this.
revision = int(module.params['revision'])
# A revision has been explicitly specified. Attempt to locate a matching revision
tasks_defs_for_revision = [td for td in existing_definitions_in_family if td['revision'] == revision]
existing = tasks_defs_for_revision[0] if len(tasks_defs_for_revision) > 0 else None
if existing and existing['status'] != "ACTIVE":
# We cannot reactivate an inactive revision
module.fail_json(msg="A task in family '%s' already exists for revision %d, but it is inactive" % (family, revision))
elif not existing:
if not existing_definitions_in_family and revision != 1:
module.fail_json(msg="You have specified a revision of %d but a created revision would be 1" % revision)
elif existing_definitions_in_family and existing_definitions_in_family[-1]['revision'] + 1 != revision:
module.fail_json(msg="You have specified a revision of %d but a created revision would be %d" %
(revision, existing_definitions_in_family[-1]['revision'] + 1))
else:
existing = None
def _right_has_values_of_left(left, right):
# Make sure the values are equivalent for everything left has
for k, v in left.items():
if not ((not v and (k not in right or not right[k])) or (k in right and v == right[k])):
# We don't care about list ordering because ECS can change things
if isinstance(v, list) and k in right:
left_list = v
right_list = right[k] or []
if len(left_list) != len(right_list):
return False
for list_val in left_list:
if list_val not in right_list:
# 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:
# 'essential' defaults to True when not specified
if k == 'essential' and v is True:
pass
else:
return False
return True
def _task_definition_matches(requested_volumes, requested_containers, requested_task_role_arn, requested_launch_type, existing_task_definition):
if td['status'] != "ACTIVE":
return None
if requested_task_role_arn != td.get('taskRoleArn', ""):
return None
if requested_launch_type is not None and requested_launch_type not in td.get('compatibilities', []):
return None
existing_volumes = td.get('volumes', []) or []
if len(requested_volumes) != len(existing_volumes):
# Nope.
return None
if len(requested_volumes) > 0:
for requested_vol in requested_volumes:
found = False
for actual_vol in existing_volumes:
if _right_has_values_of_left(requested_vol, actual_vol):
found = True
break
if not found:
return None
existing_containers = td.get('containerDefinitions', []) or []
if len(requested_containers) != len(existing_containers):
# Nope.
return None
for requested_container in requested_containers:
found = False
for actual_container in existing_containers:
if _right_has_values_of_left(requested_container, actual_container):
found = True
break
if not found:
return None
return existing_task_definition
# No revision explicitly specified. Attempt to find an active, matching revision that has all the properties requested
for td in existing_definitions_in_family:
requested_volumes = module.params['volumes'] or []
requested_containers = module.params['containers'] or []
requested_task_role_arn = module.params['task_role_arn']
requested_launch_type = module.params['launch_type']
existing = _task_definition_matches(requested_volumes, requested_containers, requested_task_role_arn, requested_launch_type, td)
if existing:
break
if existing and not module.params.get('force_create'):
# Awesome. Have an existing one. Nothing to do.
results['taskdefinition'] = existing
else:
if not module.check_mode:
# Doesn't exist. create it.
volumes = module.params.get('volumes', []) or []
results['taskdefinition'] = task_mgr.register_task(module.params['family'],
module.params['task_role_arn'],
module.params['execution_role_arn'],
module.params['network_mode'],
module.params['containers'],
volumes,
module.params['launch_type'],
module.params['cpu'],
module.params['memory'],
module.params['placement_constraints'],)
results['changed'] = True
elif module.params['state'] == 'absent':
# When de-registering a task definition, we can specify the ARN OR the family and revision.
if module.params['state'] == 'absent':
if 'arn' in module.params and module.params['arn'] is not None:
task_to_describe = module.params['arn']
elif 'family' in module.params and module.params['family'] is not None and 'revision' in module.params and \
module.params['revision'] is not None:
task_to_describe = module.params['family'] + ":" + str(module.params['revision'])
else:
module.fail_json(msg="To use task definitions, an arn or family and revision must be specified")
existing = task_mgr.describe_task(task_to_describe)
if not existing:
pass
else:
# It exists, so we should delete it and mark changed. Return info about the task definition deleted
results['taskdefinition'] = existing
if 'status' in existing and existing['status'] == "INACTIVE":
results['changed'] = False
else:
if not module.check_mode:
task_mgr.deregister_task(task_to_describe)
results['changed'] = True
module.exit_json(**results)
if __name__ == '__main__':
main()