Restore the preview host and its SSL when a custom domain is removed.
Removing a custom domain left the Ingress with a stale custom-domain rule and no preview host, so the platform preview URL 404'd. updateIngress now resolves the app's stable preview number from its latest deployment and re-emits the preview host (with cert-manager TLS) whenever no verified custom domain is set, and removeCustomDomain re-applies the Ingress. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Injectable, Logger, OnModuleInit, BadRequestException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as k8s from '@kubernetes/client-node';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
@@ -8,6 +10,7 @@ import { promisify } from 'util';
|
||||
import { PassThrough } from 'stream';
|
||||
import { ClustersService } from '../clusters/clusters.service';
|
||||
import { Application } from '../applications/entities/application.entity';
|
||||
import { Deployment } from '../deployments/entities/deployment.entity';
|
||||
import { ensureAppUrlEnv } from '../applications/app-url.util';
|
||||
import {
|
||||
AppRuntime,
|
||||
@@ -74,8 +77,27 @@ export class KubernetesService implements OnModuleInit {
|
||||
private clustersService: ClustersService,
|
||||
private helmService: HelmService,
|
||||
private registryService: RegistryService,
|
||||
@InjectRepository(Deployment)
|
||||
private deploymentsRepository: Repository<Deployment>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Stable per-app preview number, reused from the latest deployment that has
|
||||
* one. Used to restore the preview host on the Ingress (e.g. after a custom
|
||||
* domain is removed) without triggering a fresh deploy.
|
||||
*/
|
||||
private async resolvePreviewNumber(applicationId: string): Promise<string | null> {
|
||||
const existing = await this.deploymentsRepository
|
||||
.createQueryBuilder('d')
|
||||
.select('d.previewSubdomain', 'previewSubdomain')
|
||||
.where('d.applicationId = :applicationId', { applicationId })
|
||||
.andWhere('d.previewSubdomain IS NOT NULL')
|
||||
.orderBy('d.createdAt', 'DESC')
|
||||
.limit(1)
|
||||
.getRawOne<{ previewSubdomain: string }>();
|
||||
return existing?.previewSubdomain || null;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
// Helm chart is used for deployments — no local template loading needed
|
||||
}
|
||||
@@ -456,13 +478,20 @@ export class KubernetesService implements OnModuleInit {
|
||||
const customDomain = (app.customDomain && app.customDomainStatus === CustomDomainStatus.VERIFIED)
|
||||
? app.customDomain : undefined;
|
||||
|
||||
// When there's no verified custom domain, restore the stable preview host so
|
||||
// the app stays reachable (and keeps its TLS) — e.g. after a custom domain is
|
||||
// removed. buildHelmValues only emits a preview host when there's no
|
||||
// app.customDomain at all, so we only resolve it when none is set.
|
||||
const previewNumber = app.customDomain
|
||||
? null
|
||||
: await this.resolvePreviewNumber(app.id);
|
||||
|
||||
try {
|
||||
const kubeconfig = await this.getKubeconfig(app.clusterId);
|
||||
const imageUri = app.latestImageTag
|
||||
? this.registryService.normalizeImageReference(app.latestImageTag)
|
||||
: '';
|
||||
const values = this.buildHelmValues(app, imageUri);
|
||||
// Preview host is managed per-deployment; Helm chart only needs the main ingress host here.
|
||||
const values = this.buildHelmValues(app, imageUri, previewNumber);
|
||||
await this.helmService.installOrUpgrade(app.name, namespace, values, kubeconfig);
|
||||
this.logger.log(`Updated ingress for ${app.name} via Helm (customDomain: ${customDomain || 'none'})`);
|
||||
} catch (helmError: any) {
|
||||
@@ -498,7 +527,7 @@ export class KubernetesService implements OnModuleInit {
|
||||
ownerId: app.userId,
|
||||
applicationId: app.id,
|
||||
};
|
||||
await this.applyIngress(networkingApi, ctx, customDomain);
|
||||
await this.applyIngress(networkingApi, ctx, customDomain, previewNumber);
|
||||
this.logger.log(`Updated ingress for ${app.name} via K8s API (customDomain: ${customDomain || 'none'})`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user