From 5b7b4d61223b6ade6eccf2ceb9cfc9b72e2a823c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 13 Nov 2024 22:46:52 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20our=20custom?= =?UTF-8?q?=20domains=20for=20the=20API,=20same=20domain=20as=20dashboard?= =?UTF-8?q?=20(#508)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 3 +- .github/workflows/deploy-backend.yml | 2 + backend/app/core/config.py | 1 + backend/app/docker_builder.py | 97 +++++++++++++++++++++++++++- 4 files changed, 99 insertions(+), 4 deletions(-) diff --git a/.env b/.env index 61c3fff0c0..c09cc7e655 100644 --- a/.env +++ b/.env @@ -9,9 +9,10 @@ FRONTEND_HOST=http://localhost:5173 # FRONTEND_HOST=https://dashboard.fastapicloud.com API_HOST=http://localhost:8000 # In staging, this should be -# API_HOST=https://api.fastapicloud.dev +# API_HOST=https://api.fastapicloud.work # In production, this should be # API_HOST=https://api.fastapicloud.com +API_DOMAIN=api.fastapicloud.site DEPLOYMENTS_DOMAIN=fastapicloud.club diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 19f459ddbe..092659a108 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -98,6 +98,7 @@ jobs: DOMAIN: "${{ vars.DOMAIN }}" FRONTEND_HOST: "${{ vars.FRONTEND_HOST }}" API_HOST: "${{ vars.API_HOST }}" + API_DOMAIN: "${{ vars.API_DOMAIN }}" DEPLOYMENTS_DOMAIN: "${{ vars.DEPLOYMENTS_DOMAIN }}" ENVIRONMENT: ${{ matrix.environment }} PROJECT_NAME: "${{ vars.PROJECT_NAME }}" @@ -139,6 +140,7 @@ jobs: DOMAIN: "${{ vars.DOMAIN }}" FRONTEND_HOST: "${{ vars.FRONTEND_HOST }}" API_HOST: "${{ vars.API_HOST }}" + API_DOMAIN: "${{ vars.API_DOMAIN }}" DEPLOYMENTS_DOMAIN: "${{ vars.DEPLOYMENTS_DOMAIN }}" ENVIRONMENT: ${{ matrix.environment }} PROJECT_NAME: "${{ vars.PROJECT_NAME }}" diff --git a/backend/app/core/config.py b/backend/app/core/config.py index e6a3fee8cb..6c2d67a97a 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -111,6 +111,7 @@ class MainSettings(SettingsEnv): DEVICE_AUTH_POLL_INTERVAL_SECONDS: int = 5 FRONTEND_HOST: str = "http://localhost:5173" + API_DOMAIN: str = "api.fastapicloud.site" BACKEND_CORS_ORIGINS: Annotated[ list[AnyUrl] | str, diff --git a/backend/app/docker_builder.py b/backend/app/docker_builder.py index 9788a50f33..0c50a03335 100644 --- a/backend/app/docker_builder.py +++ b/backend/app/docker_builder.py @@ -98,7 +98,7 @@ def create_namespace_by_team(namespace: str) -> None: raise e -def create_or_patch_custom_object( +def create_or_patch_custom_namespaced_object( group: str, version: str, namespace: str, @@ -140,6 +140,90 @@ def create_or_patch_custom_object( raise e +def create_or_patch_custom_cluster_object( + *, + group: str, + version: str, + plural: str, + name: str, + body: dict[str, Any], +) -> None: + api_instance = get_kubernetes_client_custom_objects() + + try: + # Try to get the custom object + api_instance.get_cluster_custom_object( # type: ignore + group=group, version=version, plural=plural, name=name + ) + + # Object exists, so patch it + api_response = api_instance.patch_cluster_custom_object( # type: ignore + group=group, + version=version, + plural=plural, + name=name, + body=body, + ) + print(f"Custom object patched. Status='{api_response}'") + + except K8sApiException as e: + if e.status == 404: # Not Found + # Object doesn't exist, so create it + api_response = api_instance.create_cluster_custom_object( # type: ignore + group=group, + version=version, + plural=plural, + body=body, + ) + print(f"Custom object created. Status='{api_response}'") + else: + raise e + + +def create_custom_domain(*, namespace: str, domain: str, service_name: str) -> None: + domain_claim = { + "apiVersion": "networking.internal.knative.dev/v1alpha1", + "kind": "ClusterDomainClaim", + "metadata": { + "name": domain, + }, + "spec": { + "namespace": namespace, + }, + } + create_or_patch_custom_cluster_object( + group="networking.internal.knative.dev", + version="v1alpha1", + plural="clusterdomainclaims", + name=domain, + body=domain_claim, + ) + + domain_mapping = { + "apiVersion": "serving.knative.dev/v1beta1", + "kind": "DomainMapping", + "metadata": { + "name": domain, + "namespace": namespace, + }, + "spec": { + "ref": { + "name": service_name, + "kind": "Service", + "apiVersion": "serving.knative.dev/v1", + }, + }, + } + create_or_patch_custom_namespaced_object( + group="serving.knative.dev", + version="v1beta1", + namespace=namespace, + plural="domainmappings", + name=domain, + body=domain_mapping, + ) + + def deploy_cloud(service_name: str, image_url: str, min_scale: int = 0) -> None: main_settings = MainSettings.get_settings().model_dump( mode="json", exclude_unset=True, exclude={"all_cors_origins"} @@ -161,13 +245,20 @@ def deploy_cloud(service_name: str, image_url: str, min_scale: int = 0) -> None: env_strs = {k: str(v) for k, v in env_data.items()} + namespace = "default" + deploy_to_kubernetes( service_name, image_url, - namespace="default", + namespace=namespace, min_scale=min_scale, env=env_strs, ) + create_custom_domain( + namespace=namespace, + domain=MainSettings.get_settings().API_DOMAIN, + service_name=service_name, + ) def deploy_to_kubernetes( @@ -210,7 +301,7 @@ def deploy_to_kubernetes( ## TODO: Add resource limits and quotas by namespace create_namespace_by_team(namespace) - create_or_patch_custom_object( + create_or_patch_custom_namespaced_object( group="serving.knative.dev", version="v1", namespace=namespace,