Development environment functions

This commit is contained in:
geoffsee
2025-08-15 18:59:05 -04:00
commit e289de2bd7
58 changed files with 11955 additions and 0 deletions

15
deploy/dev/components/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
*.d.ts
*.js
node_modules
cdktf.out
cdktf.log
*terraform.*.tfstate*
.gen
.terraform
tsconfig.tsbuildinfo
!jest.config.js
!setup.js
/zitadel-values.yaml
/traefik-values.yaml
/postgres-values.yaml
/debug-fullsynth.json

View File

@@ -0,0 +1,245 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`ClusterComponentsStack Snapshot Tests should match the expected Terraform configuration snapshot 1`] = `
"{
"output": {
"admin_credentials": {
"description": "Default admin credentials",
"value": "zitadel-admin@zitadel.machine.127.0.0.1.sslip.io / Password1!"
},
"zitadel_url": {
"description": "Zitadel Console URL",
"value": "https://machine.127.0.0.1.sslip.io/ui/console?login_hint=zitadel-admin@zitadel.machine.127.0.0.1.sslip.io"
}
},
"provider": {
"helm": [
{
"kubernetes": {
"config_context": "kind-kind",
"config_path": "~/.kube/config"
}
}
],
"kubernetes": [
{
"config_context": "kind-kind",
"config_path": "~/.kube/config"
}
],
"null": [
{
}
]
},
"resource": {
"helm_release": {
"cert-manager": {
"chart": "cert-manager",
"create_namespace": true,
"name": "cert-manager",
"namespace": "cert-manager",
"repository": "oci://quay.io/jetstack/charts",
"set": [
{
"name": "crds.enabled",
"value": "true"
}
],
"version": "v1.18.2",
"wait": true
},
"postgresql": {
"chart": "postgresql",
"depends_on": [
"helm_release.traefik"
],
"name": "db",
"namespace": "default",
"repository": "https://charts.bitnami.com/bitnami",
"values": [
"https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/4-machine-user/postgres-values.yaml"
],
"version": "12.10.0",
"wait": true
},
"traefik": {
"chart": "traefik",
"create_namespace": true,
"depends_on": [
"helm_release.cert-manager"
],
"name": "traefik",
"namespace": "ingress",
"repository": "https://traefik.github.io/charts",
"values": [
"https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/99-kind-with-traefik/traefik-values.yaml"
],
"version": "36.3.0",
"wait": true
},
"zitadel": {
"chart": "zitadel",
"depends_on": [
"helm_release.postgresql"
],
"name": "my-zitadel",
"namespace": "default",
"repository": "https://charts.zitadel.com",
"values": [
"https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/4-machine-user/zitadel-values.yaml"
],
"wait": true
}
},
"null_resource": {
"completion-message": {
"depends_on": [
"null_resource.verify-zitadel"
],
"provisioner": [
{
"local-exec": {
"command": "echo 'Installation completed successfully!'",
"when": "create"
}
}
],
"triggers": {
"verification_dependency": "\${null_resource.verify-zitadel.id}"
}
},
"configure-ssl": {
"depends_on": [
"null_resource.patch-ingresses"
],
"provisioner": [
{
"local-exec": {
"command": "\\n # Extract certificate and add to system trust store\\n kubectl get secret zitadel-tls -n default -o jsonpath='{.data.tls\\\\.crt}' | base64 -d > /tmp/zitadel-cert.crt || true\\n sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/zitadel-cert.crt || true\\n ",
"when": "create"
}
}
],
"triggers": {
"patch_dependency": "\${null_resource.patch-ingresses.id}"
}
},
"create-tls-resources": {
"depends_on": [
"null_resource.wait-for-cert-manager-crds",
"helm_release.zitadel"
],
"provisioner": [
{
"local-exec": {
"command": "cat <<EOF | kubectl apply -f -\\napiVersion: cert-manager.io/v1\\nkind: ClusterIssuer\\nmetadata:\\n name: selfsigned-issuer\\nspec:\\n selfSigned: {}\\n---\\napiVersion: cert-manager.io/v1\\nkind: Certificate\\nmetadata:\\n name: zitadel-cert\\n namespace: default\\nspec:\\n secretName: zitadel-tls\\n issuerRef:\\n name: selfsigned-issuer\\n kind: ClusterIssuer\\n commonName: machine.127.0.0.1.sslip.io\\n dnsNames:\\n - machine.127.0.0.1.sslip.io\\nEOF",
"when": "create"
}
}
],
"triggers": {
"crd_dependency": "\${null_resource.wait-for-cert-manager-crds.id}",
"zitadel_dependency": "\${helm_release.zitadel.id}"
}
},
"extract-credentials": {
"depends_on": [
"null_resource.configure-ssl"
],
"provisioner": [
{
"local-exec": {
"command": "echo 'Credential extraction would run during apply'",
"when": "create"
}
}
],
"triggers": {
"ssl_dependency": "\${null_resource.configure-ssl.id}"
}
},
"patch-ingresses": {
"depends_on": [
"null_resource.wait-for-certificate"
],
"provisioner": [
{
"local-exec": {
"command": "\\n kubectl patch ingress my-zitadel -n default --type='merge' -p='{\\"spec\\":{\\"tls\\":[{\\"hosts\\":[\\"machine.127.0.0.1.sslip.io\\"],\\"secretName\\":\\"zitadel-tls\\"}]}}' || true\\n kubectl patch ingress my-zitadel-login -n default --type='merge' -p='{\\"spec\\":{\\"tls\\":[{\\"hosts\\":[\\"machine.127.0.0.1.sslip.io\\"],\\"secretName\\":\\"zitadel-tls\\"}]}}' || true\\n ",
"when": "create"
}
}
],
"triggers": {
"wait_dependency": "\${null_resource.wait-for-certificate.id}"
}
},
"verify-zitadel": {
"depends_on": [
"null_resource.extract-credentials"
],
"provisioner": [
{
"local-exec": {
"command": "echo 'Zitadel verification would run during apply'",
"when": "create"
}
}
],
"triggers": {
"credentials_dependency": "\${null_resource.extract-credentials.id}"
}
},
"wait-for-cert-manager-crds": {
"depends_on": [
"helm_release.cert-manager"
],
"provisioner": [
{
"local-exec": {
"command": "kubectl wait --for=condition=established --timeout=120s crd/clusterissuers.cert-manager.io || kubectl get crd clusterissuers.cert-manager.io",
"when": "create"
}
}
],
"triggers": {
"cert_manager_dependency": "\${helm_release.cert-manager.id}"
}
},
"wait-for-certificate": {
"depends_on": [
"null_resource.create-tls-resources"
],
"provisioner": [
{
"local-exec": {
"command": "kubectl wait --for=condition=ready certificate zitadel-cert -n default --timeout=120s || true",
"when": "create"
}
}
],
"triggers": {
"tls_resources_dependency": "\${null_resource.create-tls-resources.id}"
}
}
}
},
"terraform": {
"required_providers": {
"helm": {
"source": "helm",
"version": "2.17.0"
},
"kubernetes": {
"source": "kubernetes",
"version": "2.38.0"
},
"null": {
"source": "null",
"version": "3.2.4"
}
}
}
}"
`;

View File

@@ -0,0 +1,204 @@
// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0
import "cdktf/lib/testing/adapters/jest"; // Load types for expect matchers
import { Testing } from "cdktf";
import { ClusterComponentsStack } from "../main";
describe("ClusterComponentsStack", () => {
describe("Resource Creation", () => {
it("should create all required Helm releases", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
// Check for all Helm releases
expect(synthesized).toContain("helm_release");
expect(synthesized).toContain('"name": "cert-manager"');
expect(synthesized).toContain('"name": "traefik"');
expect(synthesized).toContain('"name": "db"');
expect(synthesized).toContain('"name": "my-zitadel"');
});
it("should create cert-manager with correct configuration", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain('"chart": "cert-manager"');
expect(synthesized).toContain('"version": "v1.18.2"');
expect(synthesized).toContain('"namespace": "cert-manager"');
expect(synthesized).toContain('"create_namespace": true');
expect(synthesized).toContain('"wait": true');
expect(synthesized).toContain('"name": "crds.enabled"');
expect(synthesized).toContain('"value": "true"');
});
it("should create Traefik with correct configuration", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain('"chart": "traefik"');
expect(synthesized).toContain('"version": "36.3.0"');
expect(synthesized).toContain('"namespace": "ingress"');
expect(synthesized).toContain("https://traefik.github.io/charts");
expect(synthesized).toContain("traefik-values.yaml");
});
it("should create PostgreSQL with correct configuration", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain('"chart": "postgresql"');
expect(synthesized).toContain('"version": "12.10.0"');
expect(synthesized).toContain("charts.bitnami.com/bitnami");
expect(synthesized).toContain("postgres-values.yaml");
});
it("should create Zitadel with correct configuration", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain('"chart": "zitadel"');
expect(synthesized).toContain("charts.zitadel.com");
expect(synthesized).toContain("zitadel-values.yaml");
});
it("should create TLS resources using kubectl apply", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
// Check for null_resource with kubectl apply command containing TLS resources
expect(synthesized).toContain("null_resource");
expect(synthesized).toContain("create-tls-resources");
expect(synthesized).toContain("kubectl apply -f -");
expect(synthesized).toContain("kind: ClusterIssuer");
expect(synthesized).toContain("name: selfsigned-issuer");
// Check for Certificate in the kubectl apply command
expect(synthesized).toContain("kind: Certificate");
expect(synthesized).toContain("name: zitadel-cert");
expect(synthesized).toContain("secretName: zitadel-tls");
expect(synthesized).toContain("machine.127.0.0.1.sslip.io");
});
it("should create null resources for operations", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain("null_resource");
expect(synthesized).toContain("kubectl wait --for=condition=ready certificate");
expect(synthesized).toContain("kubectl patch ingress");
expect(synthesized).toContain("kubectl get secret zitadel-tls");
});
});
describe("Resource Dependencies", () => {
it("should have proper resource dependencies", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
// Verify that resources have dependencies
expect(synthesized).toContain("depends_on");
});
it("should ensure correct deployment order", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
// Traefik should depend on cert-manager
const traefikSection = synthesized.match(/"traefik"[\s\S]*?"depends_on"[\s\S]*?"helm_release\.cert-manager"/);
expect(traefikSection).toBeTruthy();
// PostgreSQL should depend on Traefik
const postgresSection = synthesized.match(/"postgresql"[\s\S]*?"depends_on"[\s\S]*?"helm_release\.traefik"/);
expect(postgresSection).toBeTruthy();
// Zitadel should depend on PostgreSQL
const zitadelSection = synthesized.match(/"zitadel"[\s\S]*?"depends_on"[\s\S]*?"helm_release\.postgresql"/);
expect(zitadelSection).toBeTruthy();
});
});
describe("Providers Configuration", () => {
it("should configure all required providers", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain('"provider"');
expect(synthesized).toContain('"helm"');
expect(synthesized).toContain('"kubernetes"');
expect(synthesized).toContain('"null"');
});
it("should configure Kubernetes provider with correct context", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain('"config_path": "~/.kube/config"');
expect(synthesized).toContain('"config_context": "kind-kind"');
});
});
describe("Outputs", () => {
it("should create Terraform outputs for important information", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain('"output"');
expect(synthesized).toContain('"zitadel_url"');
expect(synthesized).toContain('"admin_credentials"');
expect(synthesized).toContain("https://machine.127.0.0.1.sslip.io/ui/console");
expect(synthesized).toContain("zitadel-admin@zitadel.machine.127.0.0.1.sslip.io");
});
});
describe("TLS Configuration", () => {
it("should create self-signed certificate issuer", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain("apiVersion: cert-manager.io/v1");
expect(synthesized).toContain("kind: ClusterIssuer");
expect(synthesized).toContain("selfSigned: {}");
});
it("should create certificate with correct DNS names", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
const synthesized = Testing.synth(stack);
expect(synthesized).toContain("commonName: machine.127.0.0.1.sslip.io");
expect(synthesized).toContain("dnsNames:");
expect(synthesized).toContain("- machine.127.0.0.1.sslip.io");
expect(synthesized).toContain("issuerRef:");
expect(synthesized).toContain("kind: ClusterIssuer");
});
});
describe("Terraform Configuration Validity", () => {
it("should generate valid Terraform configuration", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
expect(Testing.fullSynth(stack)).toBeValidTerraform();
});
});
describe("Snapshot Tests", () => {
it("should match the expected Terraform configuration snapshot", () => {
const app = Testing.app();
const stack = new ClusterComponentsStack(app, "test-stack");
expect(Testing.synth(stack)).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,15 @@
{
"language": "typescript",
"app": "npx ts-node main.ts",
"projectId": "92d0a2d7-b985-4c11-b887-5f629f3aa198",
"sendCrashReports": "false",
"terraformProviders": [
"helm@~> 2.0",
"kubernetes@~> 2.0",
"null@~> 3.0"
],
"terraformModules": [],
"context": {
}
}

View File

@@ -0,0 +1,187 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
/*
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/z_/v03l33d55fb57nrr3b1q03ch0000gq/T/jest_dz",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// coverageDirectory: undefined,
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
moduleFileExtensions: ["ts", "js", "json", "node"],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: "ts-jest",
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ["<rootDir>/setup.js"],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
testMatch: [
"**/__tests__/**/*.ts",
"**/?(*.)+(spec|test).ts"
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
testPathIgnorePatterns: ["/node_modules/", ".d.ts", ".js"],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

View File

@@ -0,0 +1,310 @@
import { Construct } from "constructs";
import { App, TerraformStack, TerraformOutput } from "cdktf";
import { HelmProvider } from "./.gen/providers/helm/provider";
import { Release } from "./.gen/providers/helm/release";
import { KubernetesProvider } from "./.gen/providers/kubernetes/provider";
import { NullProvider } from "./.gen/providers/null/provider";
import { Resource } from "./.gen/providers/null/resource";
export class ClusterComponentsStack extends TerraformStack {
constructor(scope: Construct, id: string) {
super(scope, id);
// Configure providers
new HelmProvider(this, "helm", {
kubernetes: {
configPath: "~/.kube/config",
configContext: "kind-kind",
},
});
new KubernetesProvider(this, "kubernetes", {
configPath: "~/.kube/config",
configContext: "kind-kind",
});
new NullProvider(this, "null", {});
// 1. Install cert-manager for TLS certificate management
const certManager = new Release(this, "cert-manager", {
name: "cert-manager",
repository: "oci://quay.io/jetstack/charts",
chart: "cert-manager",
version: "v1.18.2",
namespace: "cert-manager",
createNamespace: true,
wait: true,
set: [
{
name: "crds.enabled",
value: "true",
},
],
});
// 2. Install Traefik ingress controller
const traefik = new Release(this, "traefik", {
name: "traefik",
repository: "https://traefik.github.io/charts",
chart: "traefik",
version: "36.3.0",
namespace: "ingress",
createNamespace: true,
wait: true,
values: [
`logs:
general:
level: DEBUG
additionalArguments:
- "--serverstransport.insecureskipverify=true"
service:
type: NodePort
ports:
web:
nodePort: 30080
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
nodePort: 30443
ingressClass:
enabled: true
isDefaultClass: true`,
],
dependsOn: [certManager],
});
// 3. Install PostgreSQL database
const postgresql = new Release(this, "postgresql", {
name: "db",
repository: "https://charts.bitnami.com/bitnami",
chart: "postgresql",
version: "12.10.0",
namespace: "default",
wait: true,
values: [
`primary:
pgHbaConfiguration: |
host all all all trust`,
],
dependsOn: [traefik],
});
// 4. Install Zitadel
const zitadel = new Release(this, "zitadel", {
name: "my-zitadel",
repository: "https://charts.zitadel.com",
chart: "zitadel",
namespace: "default",
wait: true,
values: [
`zitadel:
masterkey: x123456789012345678901234567891y
configmapConfig:
Log:
Level: debug
ExternalDomain: machine.127.0.0.1.sslip.io
ExternalPort: 443
TLS:
Enabled: false
FirstInstance:
Org:
Machine:
Machine:
Username: zitadel-admin-sa
Name: Admin
MachineKey:
ExpirationDate: "2026-01-01T00:00:00Z"
Type: 1
# PAT:
# ExpirationDate: "2026-01-01T00:00:00Z"
Database:
Postgres:
Host: db-postgresql
Port: 5432
Database: zitadel
MaxOpenConns: 20
MaxIdleConns: 10
MaxConnLifetime: 30m
MaxConnIdleTime: 5m
User:
Username: postgres
SSL:
Mode: disable
Admin:
Username: postgres
SSL:
Mode: disable
ingress:
enabled: true
login:
ingress:
enabled: true`,
],
dependsOn: [postgresql],
});
// 5. Wait for cert-manager CRDs to be available
const waitForCertManagerCRDs = new Resource(this, "wait-for-cert-manager-crds", {
triggers: {
cert_manager_dependency: certManager.id,
},
provisioners: [
{
type: "local-exec",
command: "kubectl wait --for=condition=established --timeout=120s crd/clusterissuers.cert-manager.io || kubectl get crd clusterissuers.cert-manager.io",
when: "create",
},
],
dependsOn: [certManager],
});
// 6. Create self-signed certificate issuer and certificate using kubectl apply
const createTLSResources = new Resource(this, "create-tls-resources", {
triggers: {
crd_dependency: waitForCertManagerCRDs.id,
zitadel_dependency: zitadel.id,
},
provisioners: [
{
type: "local-exec",
command: `cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: zitadel-cert
namespace: default
spec:
secretName: zitadel-tls
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
commonName: machine.127.0.0.1.sslip.io
dnsNames:
- machine.127.0.0.1.sslip.io
EOF`,
when: "create",
},
],
dependsOn: [waitForCertManagerCRDs, zitadel],
});
// 7. Wait for certificate to be ready
const waitForCertificate = new Resource(this, "wait-for-certificate", {
triggers: {
tls_resources_dependency: createTLSResources.id,
},
provisioners: [
{
type: "local-exec",
command: "kubectl wait --for=condition=ready certificate zitadel-cert -n default --timeout=120s || true",
when: "create",
},
],
dependsOn: [createTLSResources],
});
// 8. Patch ingresses with TLS configuration
const patchIngresses = new Resource(this, "patch-ingresses", {
triggers: {
wait_dependency: waitForCertificate.id,
},
provisioners: [
{
type: "local-exec",
command: `
kubectl patch ingress my-zitadel -n default --type='merge' -p='{"spec":{"tls":[{"hosts":["machine.127.0.0.1.sslip.io"],"secretName":"zitadel-tls"}]}}' || true
kubectl patch ingress my-zitadel-login -n default --type='merge' -p='{"spec":{"tls":[{"hosts":["machine.127.0.0.1.sslip.io"],"secretName":"zitadel-tls"}]}}' || true
`,
when: "create",
},
],
dependsOn: [waitForCertificate],
});
// 9. Configure custom SSL and extract certificate
const configureSSL = new Resource(this, "configure-ssl", {
triggers: {
patch_dependency: patchIngresses.id,
},
provisioners: [
{
type: "local-exec",
command: `
# Extract certificate and add to system trust store
kubectl get secret zitadel-tls -n default -o jsonpath='{.data.tls\\.crt}' | base64 -d > ./certs/zitadel-cert.crt || true
`,
when: "create",
},
],
dependsOn: [patchIngresses],
});
// 10. Wait for Zitadel service account secret and extract credentials
const extractCredentials = new Resource(this, "extract-credentials", {
triggers: {
ssl_dependency: configureSSL.id,
},
provisioners: [
{
type: "local-exec",
command: `echo 'Credential extraction would run during apply'`,
when: "create",
},
],
dependsOn: [configureSSL],
});
// 11. Verify Zitadel accessibility
const verifyZitadel = new Resource(this, "verify-zitadel", {
triggers: {
credentials_dependency: extractCredentials.id,
},
provisioners: [
{
type: "local-exec",
command: `echo 'Zitadel verification would run during apply'`,
when: "create",
},
],
dependsOn: [extractCredentials],
});
// 12. Output success message
new Resource(this, "completion-message", {
triggers: {
verification_dependency: verifyZitadel.id,
},
provisioners: [
{
type: "local-exec",
command: `echo 'Installation completed successfully!'`,
when: "create",
},
],
dependsOn: [verifyZitadel],
});
// Output important information
new TerraformOutput(this, "zitadel_url", {
value: "https://machine.127.0.0.1.sslip.io/ui/console?login_hint=zitadel-admin@zitadel.machine.127.0.0.1.sslip.io",
description: "Zitadel Console URL",
});
new TerraformOutput(this, "admin_credentials", {
value: "zitadel-admin@zitadel.machine.127.0.0.1.sslip.io / Password1!",
description: "Default admin credentials",
});
}
}
const app = new App();
new ClusterComponentsStack(app, "cluster-components");
app.synth();

5884
deploy/dev/components/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
{
"name": "cluster-components",
"version": "1.0.0",
"main": "main.js",
"types": "main.ts",
"license": "MPL-2.0",
"private": true,
"scripts": {
"get": "cdktf get",
"synth": "cdktf synth",
"deploy": "cdktf deploy --auto-approve",
"test": "jest",
"test:watch": "jest --watch",
"upgrade": "npm i cdktf@latest cdktf-cli@latest",
"upgrade:next": "npm i cdktf@next cdktf-cli@next",
"destroy": "cdktf destroy --auto-approve"
},
"engines": {
"node": ">=20.9"
},
"dependencies": {
"@cdktf/provider-helm": "12.0.1",
"@cdktf/provider-kubernetes": "12.1.0",
"cdktf": "^0.21.0",
"constructs": "^10.4.2"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/node": "^24.3.0",
"jest": "^30.0.5",
"ts-jest": "^29.4.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
}
}

View File

@@ -0,0 +1,2 @@
const cdktf = require("cdktf");
cdktf.Testing.setupJest();

View File

@@ -0,0 +1,35 @@
{
"compilerOptions": {
"alwaysStrict": true,
"declaration": true,
"experimentalDecorators": true,
"inlineSourceMap": true,
"inlineSources": true,
"lib": [
"es2018"
],
"module": "CommonJS",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"strict": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"stripInternal": true,
"target": "ES2018",
"incremental": true,
"skipLibCheck": true
},
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"cdktf.out"
]
}