Source code for smarter.apps.infrastructure.services

"""
The infrastructure service layer: the platform's only way to reach its infrastructure.

:data:`infrastructure` gives the platform each service, whichever cloud provider implements it:

.. code-block:: python

    from smarter.apps.infrastructure.services import infrastructure

    zone, created = infrastructure.dns.get_or_create_zone("example.com")
    certificate_id, _ = infrastructure.certificates.get_or_create_certificate("example.com")
    infrastructure.kubernetes.apply_manifest(manifest)
    infrastructure.email.send_email(subject="Hello", body="...", to="user@example.com")

- ``dns``: :class:`~.dns.DNSService`, implemented by the cloud provider, e.g. AWS Route53.
- ``certificates``: :class:`~.certificates.CertificateService`, implemented by the cloud
  provider, e.g. AWS Certificate Manager.
- ``kubernetes``: :class:`~.kubernetes.KubernetesService`, implemented with kubectl, and the
  cloud provider's kubeconfig.
- ``email``: :class:`~.email.EmailService`, implemented with SMTP.
- ``provider``: the :class:`~smarter.apps.infrastructure.providers.base.CloudProvider` of
  ``smarter_settings.cloud_provider``.

Each service is resolved when it is used, not when this module is imported, so that tests can
replace it, with :func:`~smarter.apps.infrastructure.providers.configure_provider`,
:func:`~.kubernetes.configure_kubernetes` and :func:`~.email.configure_email`, and so that
importing the platform never reaches the network.

Tests of code that uses :data:`infrastructure` can also patch it where it is used, e.g.
``patch("smarter.apps.llmclient.tasks.verify_custom_domain.infrastructure")``.
"""

from typing import TYPE_CHECKING

from .base import InfrastructureService, refuse_in_unit_tests
from .certificates import Certificate, CertificateService
from .dns import DNSRecord, DNSService, DNSZone
from .email import (
    EmailService,
    InMemoryEmailService,
    SMTPEmailService,
    configure_email,
    get_email,
)
from .kubernetes import (
    KubectlKubernetesService,
    KubernetesService,
    configure_kubernetes,
    get_kubernetes,
)

if TYPE_CHECKING:
    from ..providers.base import CloudProvider


[docs] class InfrastructureServices: """The platform's infrastructure services. Use the :data:`infrastructure` instance. """ @property def provider(self) -> "CloudProvider": """The cloud provider, ``smarter_settings.cloud_provider``, unless one is configured.""" # pylint: disable=import-outside-toplevel from ..providers import get_provider return get_provider() @property def ready(self) -> bool: """Whether the cloud provider is authenticated.""" return self.provider.ready @property def dns(self) -> DNSService: """The cloud provider's DNS.""" return self.provider.dns @property def certificates(self) -> CertificateService: """The cloud provider's TLS certificates.""" return self.provider.certificates @property def kubernetes(self) -> KubernetesService: """The platform's Kubernetes cluster.""" return get_kubernetes() @property def email(self) -> EmailService: """The platform's outgoing email.""" return get_email()
infrastructure = InfrastructureServices() """The platform's infrastructure services.""" __all__ = [ "Certificate", "CertificateService", "DNSRecord", "DNSService", "DNSZone", "EmailService", "InMemoryEmailService", "InfrastructureService", "InfrastructureServices", "KubectlKubernetesService", "KubernetesService", "SMTPEmailService", "configure_email", "configure_kubernetes", "get_email", "get_kubernetes", "infrastructure", "refuse_in_unit_tests", ]