@ -137,6 +137,33 @@ class EcsTaskManager:
containerDefinitions = container_definitions , volumes = volumes )
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 ' ]
@ -145,12 +172,12 @@ def main():
argument_spec = ec2_argument_spec ( )
argument_spec . update ( 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 ' ) ,
containers = dict ( required = False , type = ' list ' ) ,
volumes = dict ( required = False , type = ' list ' )
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 ' ) ,
containers = dict ( required = False , type = ' list ' ) ,
volumes = dict ( required = False , type = ' list ' )
) )
module = AnsibleModule ( argument_spec = argument_spec , supports_check_mode = True )
@ -162,52 +189,143 @@ def main():
module . fail_json ( msg = ' boto3 is required. ' )
task_to_describe = None
# When deregistering a task, 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 " )
# When registering a task, we can specify the ARN OR
# the family and revision.
task_mgr = EcsTaskManager ( module )
results = dict ( changed = False )
if module . params [ ' state ' ] == ' present ' :
if not ' family ' in module . params :
module . fail_json ( msg = " To use task definitions, a family must be specified " )
if not ' containers ' in module . params :
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 " )
task_to_describe = module . params [ ' family ' ]
task_mgr = EcsTaskManager ( module )
existing = task_mgr . describe_task ( task_to_describe )
if ' family ' not in module . params or not module . params [ ' family ' ] :
module . fail_json ( msg = " To use task definitions, a family must be specified " )
results = dict ( changed = False )
if module . params [ ' state ' ] == ' present ' :
if existing and ' status ' in existing and existing [ ' status ' ] == " ACTIVE " :
results [ ' taskdefinition ' ] = existing
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 gurantee 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 revsion %d , but it is inactive " % ( family , revision ) )
elif not existing :
if len ( existing_definitions_in_family ) == 0 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 [ - 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 . iteritems ( ) :
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 :
return False
else :
return False
# Make sure right doesn't have anything that left doesn't
for k , v in right . iteritems ( ) :
if v and k not in left :
return False
return True
def _task_definition_matches ( requested_volumes , requested_containers , existing_task_definition ) :
if td [ ' status ' ] != " ACTIVE " :
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 . get ( ' volumes ' , [ ] ) or [ ]
requested_containers = module . params . get ( ' containers ' , [ ] ) or [ ]
existing = _task_definition_matches ( requested_volumes , requested_containers , td )
if existing :
break
if existing :
# Awesome. Have an existing one. Nothing to do.
results [ ' taskdefinition ' ] = existing
else :
if not module . check_mode :
# doesn't exist. create it.
volumes = [ ]
if ' volumes ' in module . params :
volumes = module . params [ ' volumes ' ]
if volumes is None :
volumes = [ ]
# Doesn't exist. create it.
volumes = module . params . get ( ' volumes ' , [ ] ) or [ ]
results [ ' taskdefinition ' ] = task_mgr . register_task ( module . params [ ' family ' ] ,
module . params [ ' containers ' ] , volumes )
module . params [ ' containers ' ] , volumes )
results [ ' changed ' ] = True
# delete the cloudtrai
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 cluster deleted
# 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 " :
if ' status ' in existing and existing [ ' status ' ] == " INACTIVE " :
results [ ' changed ' ] = False
else :
if not module . check_mode :