|
| 1 | +import { ApiException } from "@kubernetes/client-node"; |
1 | 2 | import { db } from "../db/index.ts"; |
| 3 | +import { svcK8s } from "../lib/cluster/kubernetes.ts"; |
| 4 | +import { createIngressConfig } from "../lib/cluster/resources/ingress.ts"; |
| 5 | +import { env } from "../lib/env.ts"; |
| 6 | +import { isRFC1123 } from "../lib/validate.ts"; |
2 | 7 | import { ValidationError } from "./common/errors.ts"; |
3 | 8 |
|
4 | 9 | export async function isSubdomainAvailable(subdomain: string) { |
5 | | - if ( |
6 | | - subdomain.length > 54 || |
7 | | - subdomain.match(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/) === null |
8 | | - ) { |
| 10 | + if (!isRFC1123(subdomain)) { |
9 | 11 | throw new ValidationError("Invalid subdomain."); |
10 | 12 | } |
11 | 13 |
|
12 | | - const appUsingSubdomain = await db.app.getAppBySubdomain(subdomain); |
13 | | - return appUsingSubdomain === null; |
| 14 | + const [appUsingSubdomain, ingressDryRun] = await Promise.all([ |
| 15 | + db.app.getAppBySubdomain(subdomain), |
| 16 | + canCreateIngress(subdomain), |
| 17 | + ]); |
| 18 | + |
| 19 | + return appUsingSubdomain === null && ingressDryRun; |
| 20 | +} |
| 21 | + |
| 22 | +/** |
| 23 | + * Does a dry-run of creating an Ingress with the specified subdomain. |
| 24 | + * @returns true if the dry-run succeeded, or false if it failed due to a request error (4xx), which indicates that the subdomain is probably taken. |
| 25 | + */ |
| 26 | +export async function canCreateIngress(subdomain: string) { |
| 27 | + const config = createIngressConfig({ |
| 28 | + createIngress: true, |
| 29 | + name: "anvilops-ingress-probe", |
| 30 | + namespace: env.CURRENT_NAMESPACE, |
| 31 | + port: 80, |
| 32 | + serviceName: "anvilops-ingress-probe", |
| 33 | + subdomain: subdomain, |
| 34 | + servicePort: 80, |
| 35 | + }); |
| 36 | + |
| 37 | + try { |
| 38 | + await svcK8s["KubernetesObjectApi"].create( |
| 39 | + config, |
| 40 | + undefined, |
| 41 | + /* dryRun = */ "All", |
| 42 | + ); |
| 43 | + return true; |
| 44 | + } catch (err) { |
| 45 | + if (err instanceof ApiException && err.code >= 400 && err.code < 500) { |
| 46 | + // The dry-run failed. This is probably due to an existing Ingress using the subdomain and path that we want to reserve. |
| 47 | + return false; |
| 48 | + } |
| 49 | + throw err; |
| 50 | + } |
14 | 51 | } |
0 commit comments