Source code for ecs_composex.cloudmap.cloudmap_helpers
# SPDX-License-Identifier: MPL-2.0
# Copyright 2020-2022 John Mille <john@compose-x.io>
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ecs_composex.mods_manager import XResourceModule
from ecs_composex.common.settings import ComposeXSettings
from ecs_composex.vpc.vpc_stack import XStack as VpcStack
from boto3.session import Session
from .cloudmap_stack import PrivateNamespace
from compose_x_common.aws.cloudmap import get_all_dns_namespaces
from compose_x_common.compose_x_common import keyisset
from troposphere.servicediscovery import PrivateDnsNamespace
from ecs_composex.cloudmap.cloudmap_params import (
LAST_DOT_RE,
PRIVATE_DNS_ZONE_ID,
PRIVATE_DNS_ZONE_NAME,
PRIVATE_NAMESPACE_ID,
ZONES_PATTERN,
)
from ecs_composex.common.logging import LOG
from ecs_composex.exceptions import ComposeBaseException, IncompatibleOptions
[docs]def resolve_lookup(lookup_resources, settings, module: XResourceModule):
"""
Lookup the ACM certificates in AWS and creates the CFN mappings for them
:param list[HostedZone] lookup_resources: List of resources to lookup
:param ecs_composex.common.settings.ComposeXSettings settings:
:param XResourceModule module:
"""
if not keyisset(module.mapping_key, settings.mappings):
settings.mappings[module.mapping_key] = {}
for resource in lookup_resources:
resource.lookup_resource(
ZONES_PATTERN,
lookup_service_discovery_namespace,
PrivateDnsNamespace.resource_type,
"",
)
resource.init_outputs()
resource.generate_cfn_mappings_from_lookup_properties()
resource.generate_outputs()
settings.mappings[module.mapping_key].update(
{resource.logical_name: resource.mappings}
)
LOG.debug(settings.mappings[module.mapping_key])
[docs]def x_cloud_lookup_and_new_vpc(settings: ComposeXSettings, vpc_stack: VpcStack):
"""
Function to ensure there is no x-cloudmap.Lookup resource and Compose-X is creating a new VPC.
The Namespace (CloudMap PrivateNamespace) cannot span across multiple VPC
:param ecs_composex.common.settings.ComposeXSettings settings:
:param vpc_stack: The VPC Stack
:raises: IncompatibleOptions
"""
from ecs_composex.cloudmap.cloudmap_stack import PrivateNamespace
lookup_namespaces = [
namespace
for namespace in settings.x_resources
if isinstance(namespace, PrivateNamespace) and namespace.lookup_properties
]
if lookup_namespaces and not vpc_stack.is_void:
raise IncompatibleOptions(
"You cannot have Compose-X Create a new VPC and use x-cloudmap.Lookup."
" Use x-vpc to re-use the VPC the PrivateNamespace is attached to",
lookup_namespaces,
)
[docs]def detect_duplicas(x_resources: list[PrivateNamespace]) -> None:
"""
Function to ensure there is no multiple resources with the same zone name
:param list[PrivateNamespace] x_resources:
"""
class DuplicateZoneName(ComposeBaseException):
pass
names = [res.zone_name for res in x_resources]
if len(names) > len(set(names)):
raise DuplicateZoneName(
"There is a duplicate of zone names. All names",
names,
"unique names",
set(names),
)
[docs]def lookup_service_discovery_namespace(
zone: PrivateNamespace, session: Session, ns_id: str = None
) -> dict:
"""
Function to find and get the PrivateDnsNamespace properties needed by other resources
:param PrivateNamespace zone:
:param boto3.session.Session session:
:param str ns_id:
:return: The properties we need
:rtype: dict
"""
client = session.client("servicediscovery")
try:
namespaces = get_all_dns_namespaces(session)
if zone.zone_name not in [z["Name"] for z in namespaces]:
raise LookupError(
"No private namespace found for zone", zone.name, zone.zone_name
)
zone_r = None
if not ns_id:
for l_zone in namespaces:
if zone.zone_name == l_zone["Name"]:
the_zone = l_zone
zone_r = client.get_namespace(Id=the_zone["Id"])
break
else:
zone_r = client.get_namespace(Id=ns_id)
if not zone_r:
raise LookupError(
f"{zone.module.res_key}.{zone.name} - Failed to lookup {zone.zone_name}"
)
properties = zone_r["Namespace"]["Properties"]
if zone_r["Namespace"]["Type"] == "HTTP":
raise TypeError(
"Unsupported CloudMap namespace HTTP. "
"Only DNS namespaces, private or public, are supported"
)
return {
PRIVATE_DNS_ZONE_ID: properties["DnsProperties"]["HostedZoneId"],
PRIVATE_DNS_ZONE_NAME: LAST_DOT_RE.sub(
"", properties["HttpProperties"]["HttpName"]
),
PRIVATE_NAMESPACE_ID: zone_r["Namespace"]["Id"],
}
except client.exceptions.NamespaceNotFound:
LOG.error(f"Namespace not found for {zone.name}")
raise
except client.exceptions.InvalidInput:
LOG.error("Failed to retrieve the zone info")
raise