Source code for ecs_composex.events.events_ecs

# SPDX-License-Identifier: MPL-2.0
# Copyright 2020-2022 John Mille <john@compose-x.io>


from compose_x_common.compose_x_common import keyisset
from troposphere import (
    AWS_ACCOUNT_ID,
    AWS_NO_VALUE,
    AWS_PARTITION,
    AWS_REGION,
    AWS_URL_SUFFIX,
    GetAtt,
    Ref,
    Sub,
)
from troposphere.applicationautoscaling import ScalingPolicy
from troposphere.events import (
    AwsVpcConfiguration,
    EcsParameters,
    NetworkConfiguration,
    Target,
)
from troposphere.iam import Policy, PolicyType, Role

from ecs_composex.common.cfn_params import Parameter
from ecs_composex.common.logging import LOG
from ecs_composex.common.troposphere_tools import add_parameters
from ecs_composex.ecs.ecs_params import (
    CLUSTER_NAME,
    FARGATE_VERSION,
    SERVICE_SCALING_TARGET,
    SERVICE_SUBNETS,
    SERVICE_T,
    TASK_T,
)
from ecs_composex.iam import add_role_boundaries
from ecs_composex.vpc.vpc_params import APP_SUBNETS, SG_ID_TYPE, SUBNETS_TYPE


[docs]def delete_service_from_template(service): """ Function to delete the ECS Service definition and scaling related resources from the template :param tuple service: """ del service[0].template.resources[SERVICE_SCALING_TARGET] stack_resources = list(service[0].template.resources.values()) for resource in stack_resources: if issubclass(type(resource), ScalingPolicy): del service[0].template.resources[resource.title] outputs = list(service[0].template.outputs.keys()) for output_name in outputs: if output_name.find(SERVICE_SCALING_TARGET) > 0: del service[0].template.outputs[output_name] service[0].ecs_service.ecs_service = None
[docs]def define_service_targets(stack, rule, cluster_arn): """ Function to define the targets for service. :param ecs_composex.events.events_stack.XStack stack: :param ecs_composex.events.events_stack.Rule rule: :param troposphere.Sub cluster_arn: :return: """ for service in rule.families_targets: service_sg_param = Parameter( f"{service[0].logical_name}GroupId", Type=SG_ID_TYPE ) service_task_def_param = Parameter( f"{service[0].logical_name}{TASK_T}", Type="String" ) service_subnets_param = Parameter( f"{service[0].logical_name}{APP_SUBNETS.title}", Type=SUBNETS_TYPE ) events_policy_doc = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["ecs:RunTask"], "Resource": [Ref(service_task_def_param)], "Condition": {"ArnLike": {"ecs:cluster": cluster_arn}}, }, { "Effect": "Allow", "Action": "iam:PassRole", "Resource": ["*"], "Condition": { "StringLike": { "iam:PassedToService": Sub( f"ecs-tasks.${{{AWS_URL_SUFFIX}}}" ) } }, }, ], } task_events_policy_doc = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["ecs:RunTask"], "Resource": [Ref(service[0].template.resources[TASK_T])], "Condition": {"ArnLike": {"ecs:cluster": cluster_arn}}, }, { "Effect": "Allow", "Action": "iam:PassRole", "Resource": ["*"], "Condition": { "StringLike": { "iam:PassedToService": Sub( f"ecs-tasks.${{{AWS_URL_SUFFIX}}}" ) } }, }, ], } events_policy = Policy( PolicyName="EventsAccess", PolicyDocument=events_policy_doc ) if "EventsAccessPolicy" not in service[0].template.resources: service[0].template.add_resource( PolicyType( "EventsAccessPolicy", PolicyName="EventsAccess", PolicyDocument=task_events_policy_doc, Roles=[ service[0].iam_manager.task_role.name, service[0].iam_manager.exec_role.name, ], ) ) role_name = f"{rule.logical_name}IamRoleToTrigger{service[0].logical_name}" if role_name not in stack.stack_template.resources: role = stack.stack_template.add_resource( Role( f"{rule.logical_name}IamRoleToTrigger{service[0].logical_name}", AssumeRolePolicyDocument={ "Version": "2012-10-17", "Statement": [ { "Sid": "TrustPolicy", "Effect": "Allow", "Principal": { "Service": Sub(f"events.${{{AWS_URL_SUFFIX}}}") }, "Action": "sts:AssumeRole", } ], }, ManagedPolicyArns=[], Policies=[events_policy], PermissionsBoundary=Ref(AWS_NO_VALUE), ) ) if service[0].iam_manager.permissions_boundary: add_role_boundaries(role, service[0].iam_manager.permissions_boundary) else: role = stack.stack_template.resources[role_name] add_parameters( stack.stack_template, [service_sg_param, service_task_def_param, service_subnets_param], ) stack.Parameters.update( { service_sg_param.title: GetAtt( service[0].logical_name, f"Outputs.{service[0].logical_name}GroupId", ), service_task_def_param.title: GetAtt( service[0].logical_name, f"Outputs.{service[0].task_definition.title}", ), service_subnets_param.title: GetAtt( service[0].logical_name, f"Outputs.{SERVICE_SUBNETS.title}", ), } ) target = Target( EcsParameters=EcsParameters( NetworkConfiguration=NetworkConfiguration( AwsVpcConfiguration=AwsVpcConfiguration( Subnets=Ref(service_subnets_param), SecurityGroups=[Ref(service_sg_param)], AssignPublicIp=service[0].service_networking.eip_assign, ) ), PlatformVersion=Ref(FARGATE_VERSION), TaskCount=service[3], TaskDefinitionArn=Ref(service_task_def_param), LaunchType="FARGATE", ), Arn=cluster_arn, Id=service[0].logical_name, RoleArn=GetAtt(role, "Arn"), ) rule.cfn_resource.Targets.append(target) if service[0].logical_name not in stack.DependsOn: stack.DependsOn.append(service[0].logical_name) if ( keyisset("DeleteDefaultService", service[4]) and SERVICE_T in service[0].template.resources ): LOG.info( f"Deleting ECS Service definition from stack for {service[0].name}" ) del service[0].template.resources[SERVICE_T] if SERVICE_SCALING_TARGET in service[0].template.resources: LOG.warning( f"Target for event {rule.logical_name} also had scaling rules. Deleting" ) delete_service_from_template(service)
[docs]def events_to_ecs(resources, services_stack, res_root_stack, settings): """ Function to map services to event rules :param resources: :param services_stack: :param res_root_stack: :param ecs_composex.common.settings.ComposeXSettings settings: :return: """ cluster_arn = Sub( f"arn:${{{AWS_PARTITION}}}:ecs:${{{AWS_REGION}}}:${{{AWS_ACCOUNT_ID}}}:" f"cluster/${{{CLUSTER_NAME.title}}}" ) rules = [ resources[res_name] for res_name in resources if resources[res_name].properties and not resources[res_name].lookup ] for rule in rules: if rule.families_targets: define_service_targets(res_root_stack, rule, cluster_arn)