def create_root_template(new_resources: list, module_res_key: str) -> Template:
    """
    Function to create the root stack template for profiles

    :param list[CodeProfiler] new_resources:
    :param str module_res_key:
    :return: The template wit the profiles
    :rtype: troposphere.Template
    """
    root_tpl = build_template(f"Root stack to manage {module_res_key}")
    for res in new_resources:
        try:
            props = import_record_properties(res.properties,
                                             ProfilingGroup,
                                             ignore_missing_required=False)
            if res.parameters and keyisset("AppendStackId", res.parameters):
                props["ProfilingGroupName"] = Sub(
                    f"{res.properties['ProfilingGroupName']}-${{StackId}}",
                    StackId=STACK_ID_SHORT,
                )
        except KeyError:
            props = import_record_properties(res.properties,
                                             ProfilingGroup,
                                             ignore_missing_required=True)
            props["ProfilingGroupName"] = Sub(
                f"{res.logical_name}-${{StackId}}", StackId=STACK_ID_SHORT)
        res.cfn_resource = ProfilingGroup(res.logical_name, **props)
        res.init_outputs()
        res.generate_outputs()
        add_outputs(root_tpl, res.outputs)
        root_tpl.add_resource(res.cfn_resource)
    return root_tpl
Ejemplo n.º 2
0
    def generate_outputs(self):
        """
        Generates a list of CFN outputs for the ECS Service and Task Definition
        """
        if self.service_networking.security_group:
            self.outputs.append(
                CfnOutput(
                    f"{self.logical_name}GroupId",
                    Value=GetAtt(self.service_networking.security_group, "GroupId"),
                )
            )
            self.outputs.append(
                CfnOutput(
                    ecs_params.SERVICE_SUBNETS.title,
                    Value=Join(",", self.service_networking.subnets_output),
                )
            )

        self.outputs.append(
            CfnOutput(self.task_definition.title, Value=Ref(self.task_definition))
        )
        if (
            self.service_scaling
            and self.service_scaling.scalable_target
            and self.service_scaling.scalable_target.title in self.template.resources
        ):
            self.outputs.append(
                CfnOutput(
                    self.service_scaling.scalable_target.title,
                    Value=Ref(self.service_scaling.scalable_target),
                )
            )
        add_outputs(self.template, self.outputs)
def define_table(table, template):
    """
    Function to create the DynamoDB table resource

    :param table:
    :type table: ecs_composex.common.compose_resources.Table
    """
    table_props = import_record_properties(table.properties, dynamodb.Table)
    table_props.update({
        "Metadata":
        metadata,
        "Tags":
        Tags(
            Name=table.name,
            ResourceName=table.logical_name,
            CreatedByComposex=True,
            RootStackName=Ref(ROOT_STACK_NAME),
        ),
    })
    cfn_table = dynamodb.Table(table.logical_name, **table_props)
    table.cfn_resource = cfn_table
    if table.scaling:
        add_autoscaling(table, template)
    table.init_outputs()
    table.generate_outputs()
    add_resource(template, table.cfn_resource)
    add_outputs(template, table.outputs)
Ejemplo n.º 4
0
def create_alarms(template: Template, new_alarms: list[Alarm]) -> None:
    """
    Main function to create new alarms
    Rules out CompositeAlarms first, creates "Simple" alarms, and then link these to ComopsiteAlarms if so declared.
    """
    for alarm in new_alarms:
        if (alarm.properties and not alarm.parameters or
            (alarm.parameters
             and not keyisset("CompositeExpression", alarm.parameters))):
            try:
                import_record_properties(
                    alarm.properties,
                    CompositeAlarm,
                    ignore_missing_required=False,
                )
            except KeyError:
                props = import_record_properties(alarm.properties, CWAlarm)
                alarm.cfn_resource = CWAlarm(alarm.logical_name, **props)
                if alarm.cfn_resource.title not in template.resources:
                    alarm.init_outputs()
                    alarm.generate_outputs()
                    add_resource(template, alarm.cfn_resource)
                    add_outputs(template, alarm.outputs)
        elif alarm.parameters and keyisset("CompositeExpression",
                                           alarm.parameters):
            continue

    add_composite_alarms(template, new_alarms)
Ejemplo n.º 5
0
def render_new_parameters(new_resources: list[SsmParameter],
                          root_stack: ComposeXStack) -> None:
    """

    :param list[SsmParameter] new_resources:
    :param ecs_composex.common.stacks.ComposeXStack root_stack:
    """
    for new_res in new_resources:
        value = None
        if (keyisset("Type", new_res.definition)
                and new_res.definition["Type"] == "SecureString"):
            raise ValueError(
                f"{new_res.name} AWS CFN does not support SecureString.")
        if new_res.parameters and keyisset("FromFile", new_res.parameters):
            value = import_value_from_file(new_res)
        if keyisset("Value", new_res.properties):
            if value:
                LOG.warn(
                    "Both Value and FromFile properties were set. Using Value from Properties"
                )
            value = new_res.properties["Value"]
        if not value:
            raise ValueError(f"{new_res.name} - Failed to determine the value")
        if keyisset("EncodeToBase64", new_res.parameters):
            value = Base64(value)
        new_res.properties.update({"Value": value})
        param_props = import_record_properties(new_res.properties,
                                               CfnSsmParameter,
                                               ignore_missing_required=False)
        new_res.cfn_resource = CfnSsmParameter(new_res.logical_name,
                                               **param_props)
        root_stack.stack_template.add_resource(new_res.cfn_resource)
        new_res.init_outputs()
        new_res.generate_outputs()
        add_outputs(root_stack.stack_template, new_res.outputs)
Ejemplo n.º 6
0
def add_service_actions(alarm, alarms_stack, target, scaling_in_policy,
                        scaling_out_policy):
    """
    Function to update the alarm properties with OKActions and AlarmActions

    :param ecs_composex.alarms.alarms_stack.Alarm alarm:
    :param ecs_composex.common.stacks.ComposeXStack alarms_stack:
    :param tuple target:
    :param scaling_in_policy:
    :param scaling_out_policy:
    """
    setattr(
        alarm,
        "Threshold",
        float(scaling_out_policy.StepScalingPolicyConfiguration.
              StepAdjustments[0].MetricIntervalLowerBound),
    )
    if not alarm.cfn_resource:
        raise AttributeError(
            f"Alarm {alarm.logical_name} has no CFN object associated")
    service_scaling_in_policy_param = Parameter(
        f"{target[0].logical_name}ScaleInPolicy", Type="String")
    service_scaling_out_policy_param = Parameter(
        f"{target[0].logical_name}ScaleOutPolicy", Type="String")
    add_parameters(
        alarms_stack.stack_template,
        [service_scaling_in_policy_param, service_scaling_out_policy_param],
    )
    add_outputs(
        target[0].template,
        [
            Output(
                f"{target[0].logical_name}ScaleInPolicy",
                Value=Ref(scaling_in_policy),
            ),
            Output(
                f"{target[0].logical_name}ScaleOutPolicy",
                Value=Ref(scaling_out_policy),
            ),
        ],
    )
    alarms_stack.Parameters.update({
        service_scaling_in_policy_param.title:
        GetAtt(
            target[0].logical_name,
            f"Outputs.{target[0].logical_name}ScaleInPolicy",
        ),
        service_scaling_out_policy_param.title:
        GetAtt(
            target[0].logical_name,
            f"Outputs.{target[0].logical_name}ScaleOutPolicy",
        ),
    })
    actions = get_alarm_actions(alarm)
    actions[0].append(Ref(service_scaling_in_policy_param))
    actions[1].append(Ref(service_scaling_out_policy_param))
Ejemplo n.º 7
0
def create_record(name, route53_zone, route53_stack, target_elbv2,
                  elbv2_stack) -> None:
    """
    Create a new RecordResource with the given DNS Name pointing to the ELB

    :param str name:
    :param ecs_composex.route53.route53_zone.HostedZone route53_zone:
    :param ecs_composex.route53.route53_stack.XStack route53_stack:
    :param ecs_composex.elbv2.elbv2_stack.Elbv2 target_elbv2:
    :param ComposeXStack elbv2_stack:
    """
    if not target_elbv2.attributes_outputs:
        target_elbv2.init_outputs()
        target_elbv2.generate_outputs()
        add_outputs(elbv2_stack.stack_template, target_elbv2.outputs)
    lb_zone_id = target_elbv2.attributes_outputs[LB_DNS_ZONE_ID]
    lb_dns_name = target_elbv2.attributes_outputs[LB_DNS_NAME]
    add_parameters(
        route53_stack.stack_template,
        [lb_zone_id["ImportParameter"], lb_dns_name["ImportParameter"]],
    )
    route53_stack.Parameters.update({
        lb_zone_id["ImportParameter"].title:
        lb_zone_id["ImportValue"],
        lb_dns_name["ImportParameter"].title:
        lb_dns_name["ImportValue"],
    })
    elbv2_alias = AliasTarget(
        HostedZoneId=Ref(lb_zone_id["ImportParameter"]),
        DNSName=Ref(lb_dns_name["ImportParameter"]),
    )
    record_props = {
        "AliasTarget": elbv2_alias,
        "Region": Ref(AWS_REGION),
        "Type": "A",
        "Name": name,
    }
    if not keyisset("SetIdentifier", record_props):
        record_props["SetIdentifier"] = Ref(AWS_STACK_NAME)
    if route53_zone.cfn_resource:
        zone_id_attribute = GetAtt(route53_zone.cfn_resource,
                                   PUBLIC_DNS_ZONE_ID.return_value)
        record_props["HostedZoneId"] = zone_id_attribute
    elif route53_zone.mappings:
        zone_id_attribute = route53_zone.attributes_outputs[PUBLIC_DNS_ZONE_ID]
        record_props["HostedZoneId"] = zone_id_attribute["ImportValue"]
    cfn_resource = RecordSetType(
        f"elbv2{target_elbv2.logical_name}Route53{record_props['Type']}{NONALPHANUM.sub('', record_props['Name'])}"[:
                                                                                                                    128],
        **record_props,
    )
    if cfn_resource.title not in route53_stack.stack_template.resources:
        route53_stack.stack_template.add_resource(cfn_resource)
    if elbv2_stack.title not in route53_stack.DependsOn:
        route53_stack.DependsOn.append(elbv2_stack.title)
Ejemplo n.º 8
0
def add_composite_alarms(template: Template, new_alarms: list[Alarm]) -> None:

    for alarm in new_alarms:
        if not alarm.cfn_resource and ((alarm.parameters and keyisset(
                "CompositeExpression", alarm.parameters)) or alarm.properties):
            alarm.is_composite = True
            create_composite_alarm(alarm, new_alarms)
            add_resource(template, alarm.cfn_resource)
            alarm.init_outputs()
            alarm.generate_outputs()
            add_outputs(template, alarm.outputs)
Ejemplo n.º 9
0
 def __init__(self, name: str, settings: ComposeXSettings, **kwargs):
     stack_template = build_template("Root stack for IAM Roles")
     add_parameters(stack_template, [CLUSTER_NAME])
     super().__init__(name, stack_template, **kwargs)
     exec_role_managed_policy = add_ecs_execution_role_managed_policy(stack_template)
     self.Parameters.update(
         {CLUSTER_NAME.title: settings.ecs_cluster.cluster_identifier}
     )
     new_roles = import_family_roles(settings, exec_role_managed_policy)
     for role in new_roles:
         self.stack_template.add_resource(role.cfn_resource)
         role.stack = self
         if not role.attributes_outputs:
             role.generate_outputs()
         add_outputs(stack_template, role.outputs)
Ejemplo n.º 10
0
def create_new_domains(new_domains, stack):
    """
    Function to create the new CFN Template for the OS Domains to create

    :param list[ecs_composex.opensearch.opensearch_stack.OpenSearchDomain] new_domains:
    :param ecs_composex.common.stacks.ComposeXStack stack:
    """
    for domain in new_domains:
        domain.set_override_subnets()
        props = import_record_properties(domain.properties,
                                         opensearchservice.Domain)
        if keyisset("VPCOptions", props) or domain.subnets_override:
            add_new_security_group(domain, props, stack)
        if domain.parameters:
            apply_domain_parameters(domain, stack, props)
        if keyisset("AdvancedSecurityOptions", props):
            correcting_required_settings(domain, props)
        validate_instance_types(domain, props)
        domain.cfn_resource = opensearchservice.Domain(domain.logical_name,
                                                       **props)
        domain.init_outputs()
        stack.stack_template.add_resource(domain.cfn_resource)
        domain.generate_outputs()
        if domain.security_group:
            domain.add_new_output_attribute(
                OS_DOMAIN_SG,
                (
                    f"{domain.logical_name}{OS_DOMAIN_SG.return_value}",
                    domain.security_group,
                    GetAtt,
                    OS_DOMAIN_SG.return_value,
                ),
            )
            domain.add_new_output_attribute(
                OS_DOMAIN_PORT,
                (
                    f"{domain.logical_name}{OS_DOMAIN_PORT.title}",
                    OS_DOMAIN_PORT.Default,
                    OS_DOMAIN_PORT.Default,
                    False,
                ),
            )
            add_parameters(stack.stack_template, [OS_DOMAIN_PORT])
        add_outputs(stack.stack_template, domain.outputs)
Ejemplo n.º 11
0
 def create_new_default_vpc(self, title, vpc_module, settings):
     """
     In case no x-vpc was specified but the deployment settings require a new VPC, allows for an easy way to set one.
     """
     self.vpc_resource = Vpc(
         name="vpc",
         definition={"Properties": {
             VPC_CIDR.title: Vpc.default_ipv4_cidr
         }},
         module=vpc_module,
         settings=settings,
     )
     template = init_vpc_template()
     self.vpc_resource.create_vpc(template, settings)
     self.is_void = False
     self.vpc_resource.init_outputs()
     super().__init__(title, stack_template=template)
     self.vpc_resource.generate_outputs()
     add_outputs(template, self.vpc_resource.outputs)
Ejemplo n.º 12
0
 def __init__(self, title, settings: ComposeXSettings,
              module: XResourceModule, **kwargs):
     self.is_void = True
     self.vpc_resource = None
     if not keyisset(module.res_key, settings.compose_content):
         LOG.warning(f"{module.res_key} - not defined. Assuming no VPC")
         self.is_void = True
     else:
         self.vpc_resource = Vpc("vpc",
                                 settings.compose_content[module.res_key],
                                 module, settings)
         if self.vpc_resource.lookup:
             self.vpc_resource.lookup_vpc(settings)
         elif self.vpc_resource.properties:
             template = init_vpc_template()
             self.vpc_resource.create_vpc(template, settings)
             self.is_void = False
             self.vpc_resource.init_outputs()
             super().__init__(title, stack_template=template, **kwargs)
             self.vpc_resource.generate_outputs()
             add_outputs(template, self.vpc_resource.outputs)
Ejemplo n.º 13
0
def handle_services_association(resource, res_root_stack, settings):
    """
    Function to handle association of listeners and targets to the LB

    :param ecs_composex.elbv2.elbv2_stack.Elbv2 resource:
    :param ecs_composex.common.settings.ComposeXSettings settings:
    :param ecs_composex.common.stacks.ComposeXStack res_root_stack:
    :return:
    """
    template = res_root_stack.stack_template
    resource.set_listeners(template)
    resource.associate_to_template(template)
    add_outputs(template, resource.outputs)
    identified = []
    for target in resource.families_targets:
        if target[1].launch_type == "EXTERNAL":
            LOG.error(
                f"x-elbv2.{resource.name} - Target family {target[0].name} uses EXTERNAL launch type. Ignoring"
            )
            continue
        tgt_arn = define_service_target_group_definition(
            resource, target[0], target[1], target[2], res_root_stack)
        for service in resource.services:
            target_name = f"{target[0].name}:{target[1].name}"
            if target_name == service["name"]:
                service["target_arn"] = tgt_arn
                identified.append(True)
    if not identified:
        LOG.error(
            f"{resource.module.res_key}.{resource.name} - No services found as targets. Skipping association"
        )
        return

    for listener in resource.listeners:
        listener.map_lb_services_to_listener_targets(resource)
    for listener in resource.listeners:
        listener.handle_certificates(settings, res_root_stack)
        listener.handle_cognito_pools(settings, res_root_stack)
        listener.define_default_actions(template)
Ejemplo n.º 14
0
def retrieve_services(settings: ComposeXSettings, services: dict,
                      x_stack: ComposeXStack) -> list[tuple]:
    """
    Function to

    :param ecs_composex.common.settings.ComposeXSettings settings:
    :param dict services:
    :param ecs_composex.common.stacks.ComposeXStack x_stack:
    :return:
    """
    services_params = []
    families_original_names = [f.name for f in settings.families.values()]
    for name, service_def in services.items():
        if name not in families_original_names:
            LOG.warn(f"Service family {name} is not defined. Skipping")
            continue
        family = get_family_from_name(settings, name)
        if family is None:
            LOG.warn(
                f"Could not identify the {name} family in {families_original_names}"
            )
            continue
        s_param = Parameter(f"{family.stack.title}{SERVICE_T}Name",
                            Type="String")
        if SERVICE_T not in family.template.outputs:
            add_outputs(
                family.template,
                [Output(s_param.title, Value=GetAtt(SERVICE_T, "Name"))],
            )
        x_stack.Parameters.update({
            s_param.title:
            GetAtt(family.stack.title, f"Outputs.{s_param.title}")
        })
        services_params.append((family.stack.title, s_param))
    add_parameters(x_stack.stack_template,
                   [value[1] for value in services_params])
    return services_params
Ejemplo n.º 15
0
def define_service_target_group(
    resource,
    family,
    service,
    resources_root_stack,
    target_definition,
):
    """
    Function to create the elbv2 target group
    :param ecs_composex.elbv2.elbv2_stack.Elbv2 resource: the ELBv2 to attach to
    :param ecs_composex.common.compose_services.ComposeService service: the service target
    :param ecs_composex.ecs.ecs_family.ComposeFamily family: the family owning the service
    :param ecs_composex.common.stacks.ComposeXStack resources_root_stack:
    :param dict target_definition: the Service definition
    :return: the target group
    :rtype: troposphere.elasticloadbalancingv2.TargetGroup
    """
    props = {}
    set_healthcheck_definition(props, target_definition)
    props["Port"] = target_definition["port"]
    props["Protocol"] = (props["HealthCheckProtocol"]
                         if not keyisset("protocol", target_definition) else
                         target_definition["protocol"])
    fix_nlb_settings(props)
    props["TargetType"] = "ip"
    import_target_group_attributes(props, target_definition, resource, service)
    validate_props_and_service_definition(props, service)
    target_group_name = f"Tgt{resource.logical_name}{family.logical_name}{service.logical_name}{props['Port']}"
    target_group = ComposeTargetGroup(
        target_group_name,
        elbv2=resource,
        family=family,
        stack=resource.stack,
        VpcId=Ref(VPC_ID),
        **props,
    )
    if target_group.title not in resources_root_stack.stack_template.resources:
        resources_root_stack.stack_template.add_resource(target_group)
    else:
        target_group = resources_root_stack.stack_template.resources[
            target_group.title]
    target_group.init_outputs()
    target_group.generate_outputs()
    add_outputs(resources_root_stack.stack_template, target_group.outputs)
    if target_group not in family.target_groups:
        family.target_groups.append(target_group)
    tgt_parameter = target_group.attributes_outputs[TGT_GROUP_ARN][
        "ImportParameter"]
    add_parameters(family.template, [tgt_parameter])
    family.stack.Parameters.update({
        tgt_parameter.title:
        target_group.attributes_outputs[TGT_GROUP_ARN]["ImportValue"],
    })
    service_lb = EcsLb(
        ContainerPort=props["Port"],
        ContainerName=service.name,
        TargetGroupArn=Ref(tgt_parameter),
    )
    family.ecs_service.lbs.append(service_lb)
    add_parameters(family.template, [ELB_GRACE_PERIOD])
    family.ecs_service.ecs_service.HealthCheckGracePeriodSeconds = Ref(
        ELB_GRACE_PERIOD)
    handle_sg_lb_ingress_to_service(resource, family, resources_root_stack)
    return target_group