# -*- coding: utf-8 -*-
# SPDX-License-Identifier: MPL-2.0
# Copyright 2020-2021 John Mille <john@compose-x.io>
"""
Core ECS Template building
"""
from troposphere import GetAtt, Ref, Sub
from troposphere.iam import PolicyType
from troposphere.logs import LogGroup
from ecs_composex.common import build_template
from ecs_composex.common.cfn_params import ROOT_STACK_NAME, ROOT_STACK_NAME_T
from ecs_composex.dns import dns_conditions, dns_params
from ecs_composex.dns.dns_conditions import (
CREATE_PUBLIC_NAMESPACE_CON,
CREATE_PUBLIC_NAMESPACE_CON_T,
)
from ecs_composex.ecs import ecs_conditions, ecs_params
from ecs_composex.ecs.ecs_params import CLUSTER_NAME, CLUSTER_NAME_T
from ecs_composex.ecs.ecs_service import Service
from ecs_composex.ecs.ecs_service_config import ServiceConfig
from ecs_composex.secrets.secrets_params import RES_KEY as SECRETS_KEY
from ecs_composex.vpc import vpc_params
[docs]def initialize_service_template(service_name):
"""Function to initialize the base template for ECS Services with all
parameters and conditions necessary for CFN to work properly
:param service_name: Name of the ecs_service as defined in ComposeX File
:type service_name: str
:return: service_template
:rtype: troposphere.Template
"""
service_tpl = build_template(
f"Template for {service_name}",
[
dns_params.PUBLIC_DNS_ZONE_NAME,
dns_params.PRIVATE_DNS_ZONE_NAME,
dns_params.PUBLIC_DNS_ZONE_ID,
dns_params.PRIVATE_DNS_ZONE_ID,
dns_params.PRIVATE_NAMESPACE_ID,
ecs_params.CLUSTER_NAME,
ecs_params.LAUNCH_TYPE,
ecs_params.ECS_CONTROLLER,
ecs_params.SERVICE_COUNT,
ecs_params.CLUSTER_SG_ID,
ecs_params.SERVICE_HOSTNAME,
ecs_params.FARGATE_CPU_RAM_CONFIG,
ecs_params.SERVICE_NAME,
ecs_params.ELB_GRACE_PERIOD,
ecs_params.FARGATE_VERSION,
ecs_params.LOG_GROUP_RETENTION,
vpc_params.VPC_ID,
vpc_params.APP_SUBNETS,
vpc_params.PUBLIC_SUBNETS,
],
)
service_tpl.add_condition(
ecs_conditions.SERVICE_COUNT_ZERO_CON_T,
ecs_conditions.SERVICE_COUNT_ZERO_CON,
)
service_tpl.add_condition(
ecs_conditions.SERVICE_COUNT_ZERO_AND_FARGATE_CON_T,
ecs_conditions.SERVICE_COUNT_ZERO_AND_FARGATE_CON,
)
service_tpl.add_condition(
ecs_conditions.USE_HOSTNAME_CON_T, ecs_conditions.USE_HOSTNAME_CON
)
service_tpl.add_condition(
ecs_conditions.NOT_USE_HOSTNAME_CON_T,
ecs_conditions.NOT_USE_HOSTNAME_CON,
)
service_tpl.add_condition(
ecs_conditions.NOT_USE_CLUSTER_SG_CON_T,
ecs_conditions.NOT_USE_CLUSTER_SG_CON,
)
service_tpl.add_condition(
ecs_conditions.USE_CLUSTER_SG_CON_T, ecs_conditions.USE_CLUSTER_SG_CON
)
service_tpl.add_condition(
ecs_conditions.USE_FARGATE_PROVIDERS_CON_T,
ecs_conditions.USE_FARGATE_PROVIDERS_CON,
)
service_tpl.add_condition(
ecs_conditions.USE_FARGATE_LT_CON_T, ecs_conditions.USE_FARGATE_LT_CON
)
service_tpl.add_condition(
ecs_conditions.USE_FARGATE_CON_T,
ecs_conditions.USE_FARGATE_CON,
)
service_tpl.add_condition(
ecs_conditions.NOT_FARGATE_CON_T, ecs_conditions.NOT_FARGATE_CON
)
service_tpl.add_condition(ecs_conditions.USE_EC2_CON_T, ecs_conditions.USE_EC2_CON)
service_tpl.add_condition(
ecs_conditions.USE_SERVICE_MODE_CON_T, ecs_conditions.USE_SERVICE_MODE_CON
)
service_tpl.add_condition(
ecs_conditions.USE_CLUSTER_MODE_CON_T, ecs_conditions.USE_CLUSTER_MODE_CON
)
service_tpl.add_condition(
ecs_conditions.USE_LAUNCH_TYPE_CON_T, ecs_conditions.USE_LAUNCH_TYPE_CON
)
service_tpl.add_condition(
CREATE_PUBLIC_NAMESPACE_CON_T, CREATE_PUBLIC_NAMESPACE_CON
)
service_tpl.add_condition(
dns_conditions.PRIVATE_ZONE_ID_CON_T, dns_conditions.PRIVATE_ZONE_ID_CON
)
service_tpl.add_condition(
dns_conditions.PRIVATE_NAMESPACE_CON_T,
dns_conditions.PRIVATE_NAMESPACE_CON,
)
return service_tpl
[docs]def create_log_group(family):
"""
Function to create a new Log Group for the services
:return:
"""
svc_log = family.template.add_resource(
LogGroup(
ecs_params.LOG_GROUP_T,
RetentionInDays=Ref(ecs_params.LOG_GROUP_RETENTION),
LogGroupName=Sub(
f"${{{ROOT_STACK_NAME.title}}}/"
f"svc/ecs/${{{ecs_params.CLUSTER_NAME_T}}}/{family.logical_name}",
),
),
)
policy = PolicyType(
f"{family.logical_name}LogGroupAccess",
PolicyName=Sub(f"CloudWatchAccessForFamily{family.logical_name}"),
PolicyDocument={
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudWatchLoggingToSpecificLogGroup",
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": [GetAtt(svc_log, "Arn")],
}
],
},
Roles=[Ref(family.exec_role.name["ImportParameter"])],
)
if (
family.template
and f"{family.logical_name}LogGroupAccess" not in family.template.resources
):
family.template.add_resource(policy)
[docs]def get_service_family_name(services_families, service_name):
"""
Function to return the root family name, representing the service stack name.
:param services_families:
:param service_name:
:return: service stack name
:rtype: str
"""
for family_name in services_families:
if service_name in services_families[family_name]:
return family_name
if service_name in services_families.keys():
return service_name
return None
[docs]def initialize_family_services(settings, family):
"""
Function to handle creation of services within the same family.
:param ecs_composex.common.settings.ComposeXSettings settings:
:return:
"""
if settings.secrets_mappings:
family.template.add_mapping(SECRETS_KEY, settings.secrets_mappings)
if SECRETS_KEY not in family.exec_role.stack.stack_template.resources:
family.exec_role.stack.stack_template.add_mapping(
SECRETS_KEY, settings.secrets_mappings
)
family.init_task_definition()
family.set_secrets_access()
family.refresh()
family.assign_policies()
family.service_config = ServiceConfig(family, settings)
family.ecs_service = Service(family, settings)
family.service_config.network.set_aws_sources(
settings,
family.logical_name,
GetAtt(family.ecs_service.sg, "GroupId"),
)
family.service_config.network.set_ext_sources_ingress(
family.logical_name, GetAtt(family.ecs_service.sg, "GroupId")
)
family.service_config.network.associate_aws_igress_rules(family.template)
family.service_config.network.associate_ext_igress_rules(family.template)
family.service_config.network.add_self_ingress(family)
family.merge_capacity_providers()
family.validate_capacity_providers(settings.ecs_cluster)
family.stack.Parameters.update(
{
ecs_params.SERVICE_NAME_T: family.logical_name,
CLUSTER_NAME_T: Ref(CLUSTER_NAME),
ROOT_STACK_NAME_T: Ref(ROOT_STACK_NAME),
}
)
family.upload_services_env_files(settings)
family.set_repository_credentials(settings)
family.set_volumes()
create_log_group(family)
family.handle_logging()
family.handle_alarms()
family.handle_prometheus()
family.validate_compute_configuration_for_task(settings)