Source code for ecs_composex.efs.efs_ecs

#  -*- coding: utf-8 -*-
# SPDX-License-Identifier: MPL-2.0
# Copyright 2020-2021 John Mille <john@compose-x.io>

from compose_x_common.compose_x_common import keyisset
from troposphere import GetAtt, Ref
from troposphere.ecs import AuthorizationConfig, EFSVolumeConfiguration, Volume
from troposphere.efs import AccessPoint, CreationInfo, PosixUser, RootDirectory
from troposphere.iam import PolicyType

from ecs_composex.common import add_parameters
from ecs_composex.ecs.ecs_params import SERVICE_T, TASK_T
from ecs_composex.efs.efs_params import FS_ARN, FS_ID, FS_MNT_PT_SG_ID, FS_PORT
from ecs_composex.tcp_resources_settings import handle_new_tcp_resource


[docs]def get_volumes(task_definition): """ Function to fetch the volumes from the task definition :param troposphere.ecs.TaskDefinition task_definition: :return: the volumes list of the task definition or new ones :rtype: list """ volumes = ( getattr(task_definition, "Volumes") if (hasattr(task_definition, "Volumes")) else [] ) if not volumes: setattr(task_definition, "Volumes", volumes) return volumes
[docs]def get_service_mount_points(service): """ Function to get the MountPoints from the container definition of a service :param ecs_composex.common.compose_services.ComposeService service: :return: list of mount points or new list :rtype: list """ mount_points = [] if not hasattr(service.container_definition, "MountPoints"): setattr(service.container_definition, "MountPoints", mount_points) else: mount_points = getattr(service.container_definition, "MountPoints") return mount_points
[docs]def add_task_iam_access_to_access_point(family, access_points, efs): """ Function to add IAM Permissions to mount to EFS via AccessPoint for ECS Task :param ecs_composex.common.compose_services.ComposeFamily family: :param list<troposphere.efs.AccessPoint> access_points: :param ecs_composex.efs.efs_stack.Efs efs: """ service_definition = family.template.resources[SERVICE_T] service_depends_on = ( getattr(service_definition, "DependsOn") if hasattr(service_definition, "DependsOn") else [] ) if not service_depends_on: setattr(service_definition, "DependsOn", service_depends_on) policy = PolicyType( f"{family.logical_name}IamAccess", PolicyName=f"IamAccessToEfs{efs.logical_name}", PolicyDocument={ "Version": "2012-10-17", "Statement": [ { "Sid": f"{family.logical_name}EfsAccess", "Effect": "Allow", "Action": [ "elasticfilesystem:ClientMount", "elasticfilesystem:ClientWrite", "elasticfilesystem:ClientRootAccess", ], "Resource": [ Ref(efs.attributes_outputs[FS_ARN]["ImportParameter"]) ], "Condition": { "StringEquals": { "elasticfilesystem:AccessPointArn": [ GetAtt(access_point, "Arn") for access_point in access_points ] } }, } ], }, Roles=[Ref(family.task_role.name["ImportParameter"])], ) service_depends_on.append(policy.title) family.template.add_resource(policy)
[docs]def add_efs_definition_to_target_family(new_efs, target): add_parameters( target[0].template, [new_efs.attributes_outputs[FS_ARN]["ImportParameter"]], ) target[0].stack.Parameters.update( { new_efs.attributes_outputs[FS_ARN][ "ImportParameter" ].title: new_efs.attributes_outputs[FS_ARN]["ImportValue"] } )
[docs]def override_service_volume(new_efs, fs_id, target, access_points, volumes): """ Function to override service volume if a specific definition was set for it """ for service in target[2]: if not service.user: continue mount_points = get_service_mount_points(service) for count, mount_pt in enumerate(mount_points): if mount_pt.SourceVolume == new_efs.volume.volume_name: container_volume_name = ( f"{new_efs.volume.volume_name}{service.logical_name}" ) setattr( mount_pt, "SourceVolume", container_volume_name, ) sub_service_specific_access_point = AccessPoint( f"{new_efs.logical_name}{service.logical_name}ServiceEfsAccessPoint", FileSystemId=Ref(fs_id), PosixUser=PosixUser( Uid=service.user, Gid=service.group if service.group else service.user, ), RootDirectory=RootDirectory( CreationInfo=CreationInfo( OwnerUid=service.user, OwnerGid=service.group if service.group else service.user, Permissions="0755", ), Path=mount_pt.ContainerPath, ), ) target[0].template.add_resource(sub_service_specific_access_point) access_points.append(sub_service_specific_access_point) volumes.append( Volume( EFSVolumeConfiguration=EFSVolumeConfiguration( FilesystemId=Ref(fs_id), AuthorizationConfig=AuthorizationConfig( AccessPointId=Ref(sub_service_specific_access_point), IAM="ENABLED", ), ), Name=container_volume_name, ) )
[docs]def set_user_to_access_points(efs, fs_id, access_points, service): """ Function to set the PosixUser to a specific access point for a specific given service """ group_id = service.group if service.group else service.user mount_points = get_service_mount_points(service) for mount_pt in mount_points: if mount_pt.SourceVolume == efs.volume.volume_name: for access_point in access_points: setattr( access_point, "PosixUser", PosixUser(Uid=service.user, Gid=group_id), ) setattr( access_point, "RootDirectory", RootDirectory( CreationInfo=CreationInfo( OwnerUid=service.user, OwnerGid=group_id, Permissions="0755", ), Path=mount_pt.ContainerPath, ), ),
[docs]def override_efs_settings(new_efs, target, fs_id_parameter, access_points, volumes): """ Function to determine if access points should be set on a per service of the task definition and update the volumes and mount points accordingly :param ecs_composex.efs.efs_stack.Efs new_efs: :param tuple target: :param ecs_composex.common.cfn_params.Parameter fs_id_parameter: :param list access_points: :param list volumes: :return: """ if [service.user for service in target[2]] and len( [service.user for service in target[2]] ) > 1: override_service_volume( new_efs, fs_id_parameter, target, access_points, volumes ) elif [service.user for service in target[2]] and len( [service.user for service in target[2]] ) == 1: for service in target[2]: if service.user: set_user_to_access_points( new_efs, fs_id_parameter, access_points, service, )
[docs]def expand_family_with_efs_volumes(efs_root_stack_title, new_efs, settings): """ Function to add the EFS Volume definition to the task definition for the service to use. :param efs_root_stack_title: Root stack title for EFS :param new_efs: :param ecs_composex.common.settings.ComposeXSettings settings: :return: """ fs_id_parameter = new_efs.attributes_outputs[FS_ID]["ImportParameter"] fs_id_getatt = new_efs.attributes_outputs[FS_ID]["ImportValue"] for target in new_efs.families_targets: access_points = [] target[0].stack.Parameters.update({fs_id_parameter.title: fs_id_getatt}) add_parameters(target[0].template, [fs_id_parameter]) task_definition = target[0].template.resources[TASK_T] efs_config_kwargs = {"FilesystemId": Ref(fs_id_parameter)} if ( new_efs.parameters and keyisset("EnforceIamAuth", new_efs.parameters) or [service.user for service in target[2]] ): add_efs_definition_to_target_family(new_efs, target) efs_access_point = target[0].template.add_resource( AccessPoint( f"{new_efs.logical_name}{target[0].logical_name}EfsAccessPoint", FileSystemId=Ref(fs_id_parameter), ) ) access_points.append(efs_access_point) efs_config_kwargs.update( { "AuthorizationConfig": AuthorizationConfig( AccessPointId=Ref(efs_access_point), IAM="ENABLED" ), "TransitEncryption": "ENABLED", } ) efs_volume_definition = Volume( EFSVolumeConfiguration=EFSVolumeConfiguration(**efs_config_kwargs), Name=new_efs.volume.volume_name, ) volumes = get_volumes(task_definition) volumes.append(efs_volume_definition) override_efs_settings(new_efs, target, fs_id_parameter, access_points, volumes) add_task_iam_access_to_access_point(target[0], access_points, new_efs)
[docs]def efs_to_ecs(resources, services_stack, res_root_stack, settings): """ Function to associate back the EFS FS to services. :param resources: :param services_stack: :param res_root_stack: :param ecs_composex.common.settings.ComposeXSettings settings: :return: """ new_resources = [ resources[res_name] for res_name in resources if not resources[res_name].lookup ] for new_res in new_resources: handle_new_tcp_resource( new_res, res_root_stack, port_parameter=FS_PORT, sg_parameter=FS_MNT_PT_SG_ID, ) expand_family_with_efs_volumes(res_root_stack.title, new_res, settings)