Compare commits

..

1 Commits

Author SHA1 Message Date
Morten Olsen
5426495be5 updates 2025-08-12 23:22:47 +02:00
12 changed files with 191 additions and 10 deletions

View File

@@ -9,4 +9,6 @@ metadata:
name: dev
namespace: dev
spec:
domain: dev.mortenolsen.pro
domain: one.dev.olsen.cloud
tls:
issuer: letsencrypt-prod

View File

@@ -16,6 +16,8 @@ import { API_VERSION } from '../../utils/consts.ts';
import { getWithNamespace } from '../../utils/naming.ts';
import { decodeSecret, encodeSecret } from '../../utils/secrets.ts';
import type { environmentSpecSchema } from '../environment/environment.schemas.ts';
import { HttpServiceInstance } from '../../instances/http-service.ts';
import type { redisServerSpecSchema } from '../redis-server/redis-server.schemas.ts';
import { authentikServerInitSecretSchema, type authentikServerSpecSchema } from './authentik-server.schemas.ts';
@@ -25,6 +27,8 @@ class AuthentikServerController extends CustomResource<typeof authentikServerSpe
#authentikSecret: SecretInstance;
#authentikRelease: HelmReleaseInstance;
#postgresSecret: ResourceReference<V1Secret>;
#httpService: HttpServiceInstance;
#redisServer: ResourceReference<CustomResourceObject<typeof redisServerSpecSchema>>;
constructor(options: CustomResourceOptions<typeof authentikServerSpecSchema>) {
super(options);
@@ -62,12 +66,24 @@ class AuthentikServerController extends CustomResource<typeof authentikServerSpe
},
HelmReleaseInstance,
);
this.#httpService = resourceService.getInstance(
{
apiVersion: API_VERSION,
kind: 'HttpService',
name: this.name,
namespace: this.namespace,
},
HttpServiceInstance,
);
this.#redisServer = new ResourceReference();
this.#postgresSecret = new ResourceReference();
this.#authentikSecret.on('changed', this.queueReconcile);
this.#authentikInitSecret.resource.on('deleted', this.queueReconcile);
this.#environment.on('changed', this.queueReconcile);
this.#authentikRelease.on('changed', this.queueReconcile);
this.#postgresSecret.on('changed', this.queueReconcile);
this.#httpService.on('changed', this.queueReconcile);
this.#redisServer.on('changed', this.queueReconcile);
}
public reconcile = async () => {
@@ -126,6 +142,9 @@ class AuthentikServerController extends CustomResource<typeof authentikServerSpe
const repoService = this.services.get(RepoService);
const redisNames = getWithNamespace(this.spec.redisServer, this.namespace);
const redisHost = `${redisNames.name}.${redisNames.namespace}.svc.cluster.local`;
await this.#authentikRelease.ensure({
metadata: {
ownerReferences: [this.ref],
@@ -165,7 +184,7 @@ class AuthentikServerController extends CustomResource<typeof authentikServerSpe
password: 'file:///postgres-creds/password',
},
redis: {
host: `redis.${this.namespace}.svc.cluster.local`,
host: redisHost,
},
},
server: {
@@ -205,6 +224,22 @@ class AuthentikServerController extends CustomResource<typeof authentikServerSpe
},
},
});
await this.#httpService.ensure({
metadata: {
ownerReferences: [this.ref],
},
spec: {
environment: this.spec.environment,
subdomain: this.spec.subdomain,
destination: {
host: `${this.name}-server.${this.namespace}.svc.cluster.local`,
port: {
number: 443,
},
},
},
});
};
}

View File

@@ -1,6 +1,7 @@
import { z } from 'zod';
const authentikServerSpecSchema = z.object({
redisServer: z.string(),
postgresCluster: z.string(),
environment: z.string(),
subdomain: z.string(),

View File

@@ -5,6 +5,7 @@ import { generateSecretDefinition } from './generate-secret/generate-secret.ts';
import { httpServiceDefinition } from './http-service/http-service.ts';
import { postgresClusterDefinition } from './postgres-cluster/postgres-cluster.ts';
import { postgresDatabaseDefinition } from './postgres-database/postgres-database.ts';
import { redisServerDefinition } from './redis-server/redis-server.ts';
const customResources = [
postgresDatabaseDefinition,
@@ -14,6 +15,7 @@ const customResources = [
postgresClusterDefinition,
authentikServerDefinition,
httpServiceDefinition,
redisServerDefinition,
];
export { customResources };

View File

@@ -12,6 +12,8 @@ import { API_VERSION } from '../../utils/consts.ts';
import { AuthentikServerInstance } from '../../instances/authentik-server.ts';
import { StorageClassInstance } from '../../instances/storageclass.ts';
import { PROVISIONER } from '../../storage-provider/storage-provider.ts';
import { RedisServerInstance } from '../../instances/redis-server.ts';
import { NamespaceService } from '../../bootstrap/namespaces/namespaces.ts';
import type { environmentSpecSchema } from './environment.schemas.ts';
@@ -24,10 +26,12 @@ class EnvironmentController extends CustomResource<typeof environmentSpecSchema>
#storageClass: StorageClassInstance;
#postgresCluster: PostgresClusterInstance;
#authentikServer: AuthentikServerInstance;
#redisServer: RedisServerInstance;
constructor(options: CustomResourceOptions<typeof environmentSpecSchema>) {
super(options);
const resourceService = this.services.get(ResourceService);
const namespaceService = this.services.get(NamespaceService);
this.#namespace = resourceService.getInstance(
{
apiVersion: 'v1',
@@ -48,8 +52,8 @@ class EnvironmentController extends CustomResource<typeof environmentSpecSchema>
{
apiVersion: 'cert-manager.io/v1',
kind: 'Certificate',
name: this.name,
namespace: this.namespace,
name: `${this.name}-tls`,
namespace: namespaceService.homelab.name,
},
CertificateInstance,
);
@@ -96,6 +100,15 @@ class EnvironmentController extends CustomResource<typeof environmentSpecSchema>
},
AuthentikServerInstance,
);
this.#redisServer = resourceService.getInstance(
{
apiVersion: API_VERSION,
kind: 'RedisServer',
name: `${this.name}-redis-server`,
namespace: this.namespace,
},
RedisServerInstance,
);
this.#gatewayCrd.on('changed', this.queueReconcile);
this.#gateway.on('changed', this.queueReconcile);
this.#certificateCrd.on('changed', this.queueReconcile);
@@ -104,6 +117,7 @@ class EnvironmentController extends CustomResource<typeof environmentSpecSchema>
this.#postgresCluster.on('changed', this.queueReconcile);
this.#authentikServer.on('changed', this.queueReconcile);
this.#storageClass.on('changed', this.queueReconcile);
this.#redisServer.on('changed', this.queueReconcile);
}
public reconcile = async () => {
@@ -120,13 +134,10 @@ class EnvironmentController extends CustomResource<typeof environmentSpecSchema>
});
if (this.#certificateCrd.ready) {
await this.#certificate.ensure({
metadata: {
ownerReferences: [this.ref],
},
spec: {
secretName: `${this.name}-tls`,
issuerRef: {
name: 'cluster-issuer',
name: this.spec.tls.issuer,
kind: 'ClusterIssuer',
},
dnsNames: [`*.${this.spec.domain}`],
@@ -143,7 +154,7 @@ class EnvironmentController extends CustomResource<typeof environmentSpecSchema>
},
spec: {
selector: {
istio: 'gateway',
istio: 'homelab-istio-gateway',
},
servers: [
{
@@ -197,8 +208,15 @@ class EnvironmentController extends CustomResource<typeof environmentSpecSchema>
environment: `${this.namespace}/${this.name}`,
subdomain: 'authentik',
postgresCluster: `${this.name}-postgres-cluster`,
redisServer: `${this.name}-redis-server`,
},
});
await this.#redisServer.ensure({
metadata: {
ownerReferences: [this.ref],
},
spec: {},
});
}
};
}

View File

@@ -2,6 +2,9 @@ import { z } from 'zod';
const environmentSpecSchema = z.object({
domain: z.string(),
tls: z.object({
issuer: z.string(),
}),
storage: z
.object({
location: z.string().optional(),

View File

@@ -66,7 +66,7 @@ class HttpServiceController extends CustomResource<typeof httpServiceSpecSchema>
},
spec: {
hosts: [`${this.spec.subdomain}.${environment.spec?.domain}`],
gateways: [`${this.#environment.current.namespace}/gateway`],
gateways: [`${this.#environment.current.namespace}/${this.#environment.current.name}`],
http: [
{
route: [

View File

@@ -0,0 +1,82 @@
import { DeploymentInstance } from '../../instances/deployment.ts';
import { ServiceInstance } from '../../instances/service.ts';
import { CustomResource } from '../../services/custom-resources/custom-resources.custom-resource.ts';
import type { CustomResourceOptions } from '../../services/custom-resources/custom-resources.custom-resource.ts';
import { ResourceService } from '../../services/resources/resources.ts';
import type { redisServerSpecSchema } from './redis-server.schemas.ts';
class RedisServerController extends CustomResource<typeof redisServerSpecSchema> {
#deployment: DeploymentInstance;
#service: ServiceInstance;
constructor(options: CustomResourceOptions<typeof redisServerSpecSchema>) {
super(options);
const resourceService = this.services.get(ResourceService);
this.#deployment = resourceService.getInstance(
{
apiVersion: 'apps/v1',
kind: 'Deployment',
name: this.name,
namespace: this.namespace,
},
DeploymentInstance,
);
this.#service = resourceService.getInstance(
{
apiVersion: 'v1',
kind: 'Service',
name: this.name,
namespace: this.namespace,
},
ServiceInstance,
);
this.#deployment.on('changed', this.queueReconcile);
this.#service.on('changed', this.queueReconcile);
}
public reconcile = async () => {
await this.#deployment.ensure({
metadata: {
ownerReferences: [this.ref],
},
spec: {
replicas: 1,
selector: {
matchLabels: {
app: this.name,
},
},
template: {
metadata: {
labels: {
app: this.name,
},
},
spec: {
containers: [
{
name: this.name,
image: 'redis:latest',
ports: [{ containerPort: 6379 }],
},
],
},
},
},
});
await this.#service.ensure({
metadata: {
ownerReferences: [this.ref],
},
spec: {
selector: {
app: this.name,
},
ports: [{ port: 6379, targetPort: 6379 }],
},
});
};
}
export { RedisServerController };

View File

@@ -0,0 +1,5 @@
import { z } from 'zod';
const redisServerSpecSchema = z.object({});
export { redisServerSpecSchema };

View File

@@ -0,0 +1,19 @@
import { createCustomResourceDefinition } from '../../services/custom-resources/custom-resources.ts';
import { GROUP } from '../../utils/consts.ts';
import { RedisServerController } from './redis-server.controller.ts';
import { redisServerSpecSchema } from './redis-server.schemas.ts';
const redisServerDefinition = createCustomResourceDefinition({
group: GROUP,
version: 'v1',
kind: 'RedisServer',
names: {
plural: 'redis-servers',
singular: 'redis-server',
},
spec: redisServerSpecSchema,
create: (options) => new RedisServerController(options),
});
export { redisServerDefinition };

View File

@@ -0,0 +1,7 @@
import type { httpServiceSpecSchema } from '../custom-resouces/http-service/http-service.schemas.ts';
import type { CustomResourceObject } from '../services/custom-resources/custom-resources.custom-resource.ts';
import { ResourceInstance } from '../services/resources/resources.instance.ts';
class HttpServiceInstance extends ResourceInstance<CustomResourceObject<typeof httpServiceSpecSchema>> {}
export { HttpServiceInstance };

View File

@@ -0,0 +1,7 @@
import type { CustomResourceObject } from '../services/custom-resources/custom-resources.custom-resource.ts';
import { ResourceInstance } from '../services/resources/resources.instance.ts';
import type { redisServerSpecSchema } from '../custom-resouces/redis-server/redis-server.schemas.ts';
class RedisServerInstance extends ResourceInstance<CustomResourceObject<typeof redisServerSpecSchema>> {}
export { RedisServerInstance };