Compare commits

..

1 Commits

Author SHA1 Message Date
Morten Olsen
32576ad37d simplify 2025-08-07 23:10:29 +02:00
9 changed files with 118 additions and 123 deletions

View File

@@ -31,16 +31,69 @@ spec:
{{- toYaml .Values.securityContext | nindent 12 }} {{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
# PostgreSQL Host
- name: POSTGRES_HOST
{{- if .Values.config.postgres.host.fromSecret.enabled }}
valueFrom:
secretKeyRef:
name: {{ .Values.config.postgres.host.fromSecret.secretName }}
key: {{ .Values.config.postgres.host.fromSecret.key }}
{{- else }}
value: {{ .Values.config.postgres.host.value | quote }}
{{- end }}
# PostgreSQL Port
- name: POSTGRES_PORT
{{- if .Values.config.postgres.port.fromSecret.enabled }}
valueFrom:
secretKeyRef:
name: {{ .Values.config.postgres.port.fromSecret.secretName }}
key: {{ .Values.config.postgres.port.fromSecret.key }}
{{- else }}
value: {{ .Values.config.postgres.port.value | quote }}
{{- end }}
# PostgreSQL User
- name: POSTGRES_USER
{{- if .Values.config.postgres.user.fromSecret.enabled }}
valueFrom:
secretKeyRef:
name: {{ .Values.config.postgres.user.fromSecret.secretName }}
key: {{ .Values.config.postgres.user.fromSecret.key }}
{{- else }}
value: {{ .Values.config.postgres.user.value | quote }}
{{- end }}
# PostgreSQL Password
- name: POSTGRES_PASSWORD
{{- if .Values.config.postgres.password.fromSecret.enabled }}
valueFrom:
secretKeyRef:
name: {{ .Values.config.postgres.password.fromSecret.secretName }}
key: {{ .Values.config.postgres.password.fromSecret.key }}
{{- else }}
value: {{ .Values.config.postgres.password.value | quote }}
{{- end }}
# Certificate Manager
- name: CERT_MANAGER
{{- if .Values.config.certManager.fromSecret.enabled }}
valueFrom:
secretKeyRef:
name: {{ .Values.config.certManager.fromSecret.secretName }}
key: {{ .Values.config.certManager.fromSecret.key }}
{{- else }}
value: {{ .Values.config.certManager.value | quote }}
{{- end }}
# Istio Gateway
- name: ISTIO_GATEWAY
{{- if .Values.config.istioGateway.fromSecret.enabled }}
valueFrom:
secretKeyRef:
name: {{ .Values.config.istioGateway.fromSecret.secretName }}
key: {{ .Values.config.istioGateway.fromSecret.key }}
{{- else }}
value: {{ .Values.config.istioGateway.value | quote }}
{{- end }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: data-volumes
mountPath: {{ .Values.storage.path }}
volumes:
- name: data-volumes
hostPath:
path: {{ .Values.storage.path }}
type: DirectoryOrCreate
{{- with .Values.nodeSelector }} {{- with .Values.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}

View File

@@ -9,11 +9,8 @@ image:
tag: main tag: main
imagePullSecrets: [] imagePullSecrets: []
nameOverride: '' nameOverride: ""
fullnameOverride: '' fullnameOverride: ""
storage:
path: /data/volumes
serviceAccount: serviceAccount:
# Specifies whether a service account should be created # Specifies whether a service account should be created
@@ -22,7 +19,7 @@ serviceAccount:
annotations: {} annotations: {}
# The name of the service account to use. # The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template # If not set and create is true, a name is generated using the fullname template
name: '' name: ""
podAnnotations: {} podAnnotations: {}
@@ -54,3 +51,53 @@ nodeSelector: {}
tolerations: [] tolerations: []
affinity: {} affinity: {}
# Configuration for the homelab operator
config:
# PostgreSQL database configuration
postgres:
host:
# Direct value (used when fromSecret.enabled is false)
value: "127.0.0.1"
# Secret reference (used when fromSecret.enabled is true)
fromSecret:
enabled: false
secretName: ""
key: "POSTGRES_HOST"
port:
value: "5432"
fromSecret:
enabled: false
secretName: ""
key: "POSTGRES_PORT"
user:
value: "postgres"
fromSecret:
enabled: false
secretName: ""
key: "POSTGRES_USER"
password:
value: ""
fromSecret:
enabled: true # Default to secret for sensitive data
secretName: "postgres-secret"
key: "POSTGRES_PASSWORD"
# Certificate manager configuration
certManager:
value: "letsencrypt-prod"
fromSecret:
enabled: false
secretName: ""
key: "CERT_MANAGER"
# Istio gateway configuration
istioGateway:
value: "istio-ingress"
fromSecret:
enabled: false
secretName: ""
key: "ISTIO_GATEWAY"

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env node
import { K8sService } from '../src/services/k8s/k8s.ts';
import { Services } from '../src/utils/service.ts';
const services = new Services();
const k8s = services.get(K8sService);
const manifests = await k8s.extensionsApi.listCustomResourceDefinition();
for (const manifest of manifests.items) {
for (const version of manifest.spec.versions) {
console.log(`group: ${manifest.spec.group}, plural: ${manifest.spec.names.plural}, version: ${version.name}`);
}
}

View File

@@ -37,8 +37,8 @@ class GenerateSecretResource extends CustomResource<typeof generateSecretSpecSch
const current = decodeSecret(this.#secretResource.data) || {}; const current = decodeSecret(this.#secretResource.data) || {};
const expected = { const expected = {
...secrets,
...current, ...current,
...secrets,
}; };
if (!isDeepSubset(current, expected)) { if (!isDeepSubset(current, expected)) {

View File

@@ -9,7 +9,6 @@ const postgresDatabaseSecretSchema = z.object({
port: z.string(), port: z.string(),
user: z.string(), user: z.string(),
password: z.string(), password: z.string(),
database: z.string().optional(),
}); });
const postgresDatabaseConnectionSecretSchema = z.object({ const postgresDatabaseConnectionSecretSchema = z.object({

View File

@@ -95,7 +95,6 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
port: serverSecretData.data.port, port: serverSecretData.data.port,
user: this.#userName, user: this.#userName,
database: this.#dbName, database: this.#dbName,
...databaseSecretData.data,
}; };
if (!isDeepSubset(databaseSecretData.data, expectedSecret)) { if (!isDeepSubset(databaseSecretData.data, expectedSecret)) {
@@ -133,7 +132,7 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
}; };
} }
const secretData = postgresDatabaseConnectionSecretSchema.safeParse(decodeSecret(this.#databaseSecret.data)); const secretData = postgresDatabaseConnectionSecretSchema.safeParse(decodeSecret(this.#serverSecret.current?.data));
if (!secretData.success || !secretData.data) { if (!secretData.success || !secretData.data) {
return { return {
ready: false, ready: false,
@@ -146,7 +145,6 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
const database = postgresService.get({ const database = postgresService.get({
...connectionSecretData.data, ...connectionSecretData.data,
port: connectionSecretData.data.port ? Number(connectionSecretData.data.port) : 5432, port: connectionSecretData.data.port ? Number(connectionSecretData.data.port) : 5432,
database: connectionSecretData.data.database,
}); });
await database.upsertRole({ await database.upsertRole({
name: secretData.data.user, name: secretData.data.user,
@@ -168,8 +166,8 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
} }
this.#updateSecret(); this.#updateSecret();
await Promise.allSettled([ await Promise.allSettled([
this.reconcileSubresource(DATABASE_READY_CONDITION, this.#reconcileDatabase), await this.reconcileSubresource(DATABASE_READY_CONDITION, this.#reconcileDatabase),
this.reconcileSubresource(SECRET_READY_CONDITION, this.#reconcileSecret), await this.reconcileSubresource(SECRET_READY_CONDITION, this.#reconcileSecret),
]); ]);
const secretReady = this.conditions.get(SECRET_READY_CONDITION)?.status === 'True'; const secretReady = this.conditions.get(SECRET_READY_CONDITION)?.status === 'True';

View File

@@ -5,7 +5,6 @@ import { Services } from './utils/service.ts';
import { CustomResourceService } from './services/custom-resources/custom-resources.ts'; import { CustomResourceService } from './services/custom-resources/custom-resources.ts';
import { WatcherService } from './services/watchers/watchers.ts'; import { WatcherService } from './services/watchers/watchers.ts';
import { customResources } from './custom-resouces/custom-resources.ts'; import { customResources } from './custom-resouces/custom-resources.ts';
import { StorageProvider } from './storage-provider/storage-provider.ts';
process.on('uncaughtException', (error) => { process.on('uncaughtException', (error) => {
console.log('UNCAUGHT EXCEPTION'); console.log('UNCAUGHT EXCEPTION');
@@ -30,8 +29,6 @@ process.on('unhandledRejection', (error) => {
const services = new Services(); const services = new Services();
const watcherService = services.get(WatcherService); const watcherService = services.get(WatcherService);
const storageProvider = services.get(StorageProvider);
await storageProvider.start();
await watcherService await watcherService
.create({ .create({
path: '/apis/apiextensions.k8s.io/v1/customresourcedefinitions', path: '/apis/apiextensions.k8s.io/v1/customresourcedefinitions',

View File

@@ -10,7 +10,6 @@ type PostgresInstanceOptions = {
port?: number; port?: number;
user: string; user: string;
password: string; password: string;
database?: string;
}; };
class PostgresInstance { class PostgresInstance {
@@ -24,7 +23,6 @@ class PostgresInstance {
user: process.env.FORCE_PG_USER ?? options.user, user: process.env.FORCE_PG_USER ?? options.user,
password: process.env.FORCE_PG_PASSWORD ?? options.password, password: process.env.FORCE_PG_PASSWORD ?? options.password,
port: process.env.FORCE_PG_PORT ? parseInt(process.env.FORCE_PG_PORT) : options.port, port: process.env.FORCE_PG_PORT ? parseInt(process.env.FORCE_PG_PORT) : options.port,
database: options.database,
}, },
}); });
} }

View File

@@ -1,82 +0,0 @@
import { mkdir } from 'fs/promises';
import { V1PersistentVolume, type V1PersistentVolumeClaim } from '@kubernetes/client-node';
import { Watcher, WatcherService } from '../services/watchers/watchers.ts';
import type { Services } from '../utils/service.ts';
import { ResourceService, type Resource } from '../services/resources/resources.ts';
const PROVISIONER = 'reuse-local-path-provisioner';
class StorageProvider {
#watcher: Watcher<V1PersistentVolumeClaim>;
#services: Services;
constructor(services: Services) {
this.#services = services;
const watchService = this.#services.get(WatcherService);
this.#watcher = watchService.create({
path: '/api/v1/persistentvolumeclaims',
transform: (manifest) => ({
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
...manifest,
}),
list: async (k8s) => {
const current = await k8s.api.listPersistentVolumeClaimForAllNamespaces();
return current;
},
verbs: ['add', 'update', 'delete'],
});
this.#watcher.on('changed', this.#handleChange);
}
#handleChange = async (pvc: Resource<V1PersistentVolumeClaim>) => {
if (pvc.metadata?.annotations?.['volume.kubernetes.io/storage-provisioner'] !== PROVISIONER) {
return;
}
const target = `/data/volumes/${pvc.namespace}/${pvc.name}`;
try {
await mkdir(target, { recursive: true });
} catch (err) {
console.error(err);
}
const resourceService = this.#services.get(ResourceService);
const pv = resourceService.get<V1PersistentVolume>({
apiVersion: 'v1',
kind: 'PersistentVolume',
name: `${pvc.namespace}-${pvc.name}`,
});
await pv.load();
await pv.patch({
metadata: {
labels: {
provisioner: PROVISIONER,
},
},
spec: {
hostPath: {
path: target,
},
capacity: {
storage: pvc.spec?.resources?.requests?.storage ?? '1Gi',
},
persistentVolumeReclaimPolicy: 'Retain',
accessModes: pvc.spec?.accessModes,
claimRef: {
uid: pvc.metadata?.uid,
resourceVersion: pvc.metadata?.resourceVersion,
apiVersion: pvc.apiVersion,
name: pvc.name,
namespace: pvc.namespace,
},
},
});
};
public start = async () => {
await this.#watcher.start();
};
}
export { StorageProvider };