From 4f5fbcf83a3d1a26f45884e93e88137e6a95a5db Mon Sep 17 00:00:00 2001 From: Shahab Dogar Date: Sat, 22 Nov 2025 23:18:56 +0500 Subject: [PATCH] feat: Utils | add public and internal ingress routes --- utils/index.ts | 6 +- utils/traefik/index.ts | 4 +- utils/traefik/ingress/index.ts | 2 + utils/traefik/{ => ingress}/ingress.ts | 10 ++- utils/traefik/ingress/internalIngress.ts | 63 +++++++++++++++++++ utils/traefik/ingress/publicIngress.ts | 63 +++++++++++++++++++ utils/traefik/ingressTCP/index.ts | 1 + utils/traefik/{ => ingressTCP}/ingress-tcp.ts | 27 +++----- 8 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 utils/traefik/ingress/index.ts rename utils/traefik/{ => ingress}/ingress.ts (90%) create mode 100644 utils/traefik/ingress/internalIngress.ts create mode 100644 utils/traefik/ingress/publicIngress.ts create mode 100644 utils/traefik/ingressTCP/index.ts rename utils/traefik/{ => ingressTCP}/ingress-tcp.ts (82%) diff --git a/utils/index.ts b/utils/index.ts index 00c25ae..9d2dacd 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,4 +1,8 @@ export { CloudflareCertificate } from "./cert-manager"; export { OnePasswordSecret } from "./1password-secret"; -export { IngressRoute, IngressRouteTcp } from "./traefik"; +export { + PublicIngressRoute, + InternalIngressRoute, + IngressRouteTcp, +} from "./traefik"; export { LonghornPvc } from "./longhorn"; diff --git a/utils/traefik/index.ts b/utils/traefik/index.ts index 4c6181b..f528f5e 100644 --- a/utils/traefik/index.ts +++ b/utils/traefik/index.ts @@ -1,2 +1,2 @@ -export { IngressRoute } from "./ingress"; -export { IngressRouteTcp } from "./ingress-tcp"; +export { PublicIngressRoute, InternalIngressRoute } from "./ingress"; +export { IngressRouteTcp } from "./ingressTCP"; diff --git a/utils/traefik/ingress/index.ts b/utils/traefik/ingress/index.ts new file mode 100644 index 0000000..3b60ac8 --- /dev/null +++ b/utils/traefik/ingress/index.ts @@ -0,0 +1,2 @@ +export { PublicIngressRoute } from "./publicIngress"; +export { InternalIngressRoute } from "./internalIngress"; diff --git a/utils/traefik/ingress.ts b/utils/traefik/ingress/ingress.ts similarity index 90% rename from utils/traefik/ingress.ts rename to utils/traefik/ingress/ingress.ts index e5e2063..3f51d6e 100644 --- a/utils/traefik/ingress.ts +++ b/utils/traefik/ingress/ingress.ts @@ -2,11 +2,12 @@ import { Construct } from "constructs"; import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest"; import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; -import { CloudflareCertificate } from "../cert-manager"; +import { CloudflareCertificate } from "../../cert-manager"; -type IngressRouteOptions = { +export type IngressRouteOptions = { provider: KubernetesProvider; namespace: string; + name: string; /** Hostname for this route (e.g. npm.dogar.dev) */ host: string; @@ -26,9 +27,6 @@ type IngressRouteOptions = { /** Extra middlewares (traefik format: namespace/name) */ middlewares?: string[]; - - /** Name override (otherwise auto) */ - name?: string; }; export class IngressRoute extends Construct { @@ -37,7 +35,7 @@ export class IngressRoute extends Construct { constructor(scope: Construct, id: string, opts: IngressRouteOptions) { super(scope, id); - const name = opts.name ?? `route-${opts.host.replace(/\./g, "-")}`; + const name = opts.name; const path = opts.path ?? "/"; const entryPoints = opts.entryPoints ?? ["websecure"]; diff --git a/utils/traefik/ingress/internalIngress.ts b/utils/traefik/ingress/internalIngress.ts new file mode 100644 index 0000000..020f493 --- /dev/null +++ b/utils/traefik/ingress/internalIngress.ts @@ -0,0 +1,63 @@ +import { Construct } from "constructs"; +import { IngressRoute, IngressRouteOptions } from "./ingress"; +import { DataTerraformRemoteStateS3 } from "cdktf"; +import { DataKubernetesNamespaceV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-namespace-v1"; + +type InternalIngressRouteOptions = Omit< + IngressRouteOptions, + "entryPoints" | "tlsSecretName" | "middlewares" +>; + +export class InternalIngressRoute extends Construct { + constructor(scope: Construct, id: string, opts: InternalIngressRouteOptions) { + super(scope, id); + + const r2Endpoint = `${process.env.ACCOUNT_ID!}.r2.cloudflarestorage.com`; + + const coreServicesState = new DataTerraformRemoteStateS3( + this, + "core-services-state", + { + usePathStyle: true, + skipRegionValidation: true, + skipCredentialsValidation: true, + skipRequestingAccountId: true, + skipS3Checksum: true, + encrypt: true, + bucket: "terraform-state", + key: "core-services/terraform.tfstate", + endpoints: { + s3: `https://${r2Endpoint}`, + }, + region: "auto", + accessKey: process.env.ACCESS_KEY, + secretKey: process.env.SECRET_KEY, + }, + ); + const namespaceName = coreServicesState.getString("namespace-output"); + const namespaceResource = new DataKubernetesNamespaceV1( + this, + "core-services-namespace", + { + provider: opts.provider, + metadata: { + name: namespaceName, + }, + }, + ); + const namespace = namespaceResource.metadata.name; + + new IngressRoute(this, opts.name, { + provider: opts.provider, + namespace: opts.namespace, + host: opts.host, + path: opts.path ?? "/", + serviceName: opts.serviceName, + servicePort: opts.servicePort, + entryPoints: ["websecure"], + tlsSecretName: `${opts.name}-tls`, + middlewares: [`${namespace}/ip-allow-list`], + name: opts.name, + }); + } +} diff --git a/utils/traefik/ingress/publicIngress.ts b/utils/traefik/ingress/publicIngress.ts new file mode 100644 index 0000000..eb06c25 --- /dev/null +++ b/utils/traefik/ingress/publicIngress.ts @@ -0,0 +1,63 @@ +import { Construct } from "constructs"; +import { IngressRoute, IngressRouteOptions } from "./ingress"; +import { DataTerraformRemoteStateS3 } from "cdktf"; +import { DataKubernetesNamespaceV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-namespace-v1"; + +type PublicIngressRouteOptions = Omit< + IngressRouteOptions, + "entryPoints" | "tlsSecretName" | "middlewares" +>; + +export class PublicIngressRoute extends Construct { + constructor(scope: Construct, id: string, opts: PublicIngressRouteOptions) { + super(scope, id); + + const r2Endpoint = `${process.env.ACCOUNT_ID!}.r2.cloudflarestorage.com`; + + const coreServicesState = new DataTerraformRemoteStateS3( + this, + "core-services-state", + { + usePathStyle: true, + skipRegionValidation: true, + skipCredentialsValidation: true, + skipRequestingAccountId: true, + skipS3Checksum: true, + encrypt: true, + bucket: "terraform-state", + key: "core-services/terraform.tfstate", + endpoints: { + s3: `https://${r2Endpoint}`, + }, + region: "auto", + accessKey: process.env.ACCESS_KEY, + secretKey: process.env.SECRET_KEY, + }, + ); + const namespaceName = coreServicesState.getString("namespace-output"); + const namespaceResource = new DataKubernetesNamespaceV1( + this, + "core-services-namespace", + { + provider: opts.provider, + metadata: { + name: namespaceName, + }, + }, + ); + const namespace = namespaceResource.metadata.name; + + new IngressRoute(this, opts.name, { + provider: opts.provider, + namespace: opts.namespace, + host: opts.host, + path: opts.path ?? "/", + serviceName: opts.serviceName, + servicePort: opts.servicePort, + entryPoints: ["websecure"], + tlsSecretName: `${opts.name}-tls`, + middlewares: [`${namespace}/rate-limit`], + name: opts.name, + }); + } +} diff --git a/utils/traefik/ingressTCP/index.ts b/utils/traefik/ingressTCP/index.ts new file mode 100644 index 0000000..d113d74 --- /dev/null +++ b/utils/traefik/ingressTCP/index.ts @@ -0,0 +1 @@ +export { IngressRouteTcp } from "./ingress-tcp"; diff --git a/utils/traefik/ingress-tcp.ts b/utils/traefik/ingressTCP/ingress-tcp.ts similarity index 82% rename from utils/traefik/ingress-tcp.ts rename to utils/traefik/ingressTCP/ingress-tcp.ts index 96c3ffc..3559599 100644 --- a/utils/traefik/ingress-tcp.ts +++ b/utils/traefik/ingressTCP/ingress-tcp.ts @@ -4,6 +4,13 @@ import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider"; type IngressRouteTcpOptions = { provider: KubernetesProvider; + name: string; + + /** + * Match rule. + * Default is `HostSNI(\`*\`)` which is correct for most TCP services. + */ + match: string; /** Namespace where the IngressRouteTCP will be created */ namespace: string; @@ -16,15 +23,6 @@ type IngressRouteTcpOptions = { /** Backend service port */ servicePort: number; - - /** - * Match rule. - * Default is `HostSNI(\`*\`)` which is correct for most TCP services. - */ - match?: string; - - /** Name override (CR name) */ - name?: string; }; export class IngressRouteTcp extends Construct { @@ -33,14 +31,7 @@ export class IngressRouteTcp extends Construct { constructor(scope: Construct, id: string, opts: IngressRouteTcpOptions) { super(scope, id); - const name = - opts.name ?? - `tcp-${opts.entryPoint}-${opts.serviceName}`.replace( - /[^a-zA-Z0-9-]/g, - "", - ); - - const matchRule = opts.match ?? "HostSNI(`*`)"; + const { name, match } = opts; this.manifest = new Manifest(this, name, { provider: opts.provider, @@ -55,7 +46,7 @@ export class IngressRouteTcp extends Construct { entryPoints: [opts.entryPoint], routes: [ { - match: matchRule, + match, services: [ { name: opts.serviceName,