mirror of
https://github.com/morten-olsen/homelab-operator.git
synced 2026-02-08 01:36:28 +01:00
updates
This commit is contained in:
@@ -13,12 +13,9 @@ import { decodeSecret, encodeSecret } from '../../utils/secrets.ts';
|
||||
import { CONTROLLED_LABEL } from '../../utils/consts.ts';
|
||||
import { isDeepSubset } from '../../utils/objects.ts';
|
||||
import { AuthentikService } from '../../services/authentik/authentik.service.ts';
|
||||
import { authentikServerSecretSchema } from '../authentik-server/authentik-server.schemas.ts';
|
||||
|
||||
import {
|
||||
authentikClientSecretSchema,
|
||||
authentikClientServerSecretSchema,
|
||||
type authentikClientSpecSchema,
|
||||
} from './authentik-client.schemas.ts';
|
||||
import { authentikClientSecretSchema, type authentikClientSpecSchema } from './authentik-client.schemas.ts';
|
||||
|
||||
class AuthentikClientResource extends CustomResource<typeof authentikClientSpecSchema> {
|
||||
#serverSecret: ResourceReference<V1Secret>;
|
||||
@@ -43,7 +40,7 @@ class AuthentikClientResource extends CustomResource<typeof authentikClientSpecS
|
||||
}
|
||||
|
||||
#updateResouces = () => {
|
||||
const serverSecretNames = getWithNamespace(this.spec.secretRef, this.namespace);
|
||||
const serverSecretNames = getWithNamespace(`${this.spec.server}-server`, this.namespace);
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
this.#serverSecret.current = resourceService.get({
|
||||
apiVersion: 'v1',
|
||||
@@ -62,7 +59,7 @@ class AuthentikClientResource extends CustomResource<typeof authentikClientSpecS
|
||||
message: 'Server or server secret not found',
|
||||
};
|
||||
}
|
||||
const serverSecretData = authentikClientServerSecretSchema.safeParse(decodeSecret(serverSecret.data));
|
||||
const serverSecretData = authentikServerSecretSchema.safeParse(decodeSecret(serverSecret.data));
|
||||
if (!serverSecretData.success || !serverSecretData.data) {
|
||||
return {
|
||||
ready: false,
|
||||
@@ -118,7 +115,7 @@ class AuthentikClientResource extends CustomResource<typeof authentikClientSpecS
|
||||
};
|
||||
}
|
||||
|
||||
const serverSecretData = authentikClientServerSecretSchema.safeParse(decodeSecret(serverSecret.data));
|
||||
const serverSecretData = authentikServerSecretSchema.safeParse(decodeSecret(serverSecret.data));
|
||||
if (!serverSecretData.success || !serverSecretData.data) {
|
||||
return {
|
||||
ready: false,
|
||||
@@ -139,7 +136,7 @@ class AuthentikClientResource extends CustomResource<typeof authentikClientSpecS
|
||||
const authentikService = this.services.get(AuthentikService);
|
||||
const authentikServer = authentikService.get({
|
||||
url: {
|
||||
internal: `${serverSecretData.data.name}.${serverSecret.namespace}.svc.cluster.local`,
|
||||
internal: `http://${serverSecretData.data.host}`,
|
||||
external: serverSecretData.data.url,
|
||||
},
|
||||
token: serverSecretData.data.token,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ClientTypeEnum, SubModeEnum } from '@goauthentik/api';
|
||||
import { z } from 'zod';
|
||||
|
||||
const authentikClientSpecSchema = z.object({
|
||||
secretRef: z.string(),
|
||||
server: z.string(),
|
||||
subMode: z.enum(SubModeEnum).optional(),
|
||||
clientType: z.enum(ClientTypeEnum).optional(),
|
||||
redirectUris: z.array(
|
||||
@@ -13,12 +13,6 @@ const authentikClientSpecSchema = z.object({
|
||||
),
|
||||
});
|
||||
|
||||
const authentikClientServerSecretSchema = z.object({
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
token: z.string(),
|
||||
});
|
||||
|
||||
const authentikClientSecretSchema = z.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string().optional(),
|
||||
@@ -31,4 +25,4 @@ const authentikClientSecretSchema = z.object({
|
||||
jwks: z.string(),
|
||||
});
|
||||
|
||||
export { authentikClientSpecSchema, authentikClientSecretSchema, authentikClientServerSecretSchema };
|
||||
export { authentikClientSpecSchema, authentikClientSecretSchema };
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import type { V1Secret } from '@kubernetes/client-node';
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
import {
|
||||
CustomResource,
|
||||
type CustomResourceOptions,
|
||||
} from '../../services/custom-resources/custom-resources.custom-resource.ts';
|
||||
import { ResourceService, type Resource } from '../../services/resources/resources.ts';
|
||||
import type { ValueReference } from '../../services/value-reference/value-reference.instance.ts';
|
||||
import { ValueReferenceService } from '../../services/value-reference/value-reference.ts';
|
||||
import { decodeSecret, encodeSecret } from '../../utils/secrets.ts';
|
||||
|
||||
import type { authentikConnectionSpecSchema } from './authentik-connection.schemas.ts';
|
||||
|
||||
class AuthentikConnectionResource extends CustomResource<typeof authentikConnectionSpecSchema> {
|
||||
#name: ValueReference;
|
||||
#url: ValueReference;
|
||||
#token: ValueReference;
|
||||
#secret: Resource<V1Secret>;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof authentikConnectionSpecSchema>) {
|
||||
super(options);
|
||||
const valueReferenceService = this.services.get(ValueReferenceService);
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
|
||||
this.#name = valueReferenceService.get(this.namespace);
|
||||
this.#url = valueReferenceService.get(this.namespace);
|
||||
this.#token = valueReferenceService.get(this.namespace);
|
||||
this.#secret = resourceService.get({
|
||||
apiVersion: 'v1',
|
||||
kind: 'Secret',
|
||||
name: `${this.name}-authentik-server`,
|
||||
namespace: this.namespace,
|
||||
});
|
||||
|
||||
this.#name.on('changed', this.queueReconcile);
|
||||
this.#url.on('changed', this.queueReconcile);
|
||||
this.#token.on('changed', this.queueReconcile);
|
||||
this.#secret.on('changed', this.queueReconcile);
|
||||
}
|
||||
|
||||
#updateResources = () => {
|
||||
this.#name.ref = this.spec.name;
|
||||
this.#url.ref = this.spec.url;
|
||||
this.#token.ref = this.spec.token;
|
||||
};
|
||||
|
||||
public reconcile = async () => {
|
||||
this.#updateResources();
|
||||
const name = this.#name.value;
|
||||
const url = this.#url.value;
|
||||
const token = this.#token.value;
|
||||
if (!name) {
|
||||
return await this.conditions.set('Ready', {
|
||||
status: 'False',
|
||||
reason: 'MissingName',
|
||||
});
|
||||
}
|
||||
if (!url) {
|
||||
return await this.conditions.set('Ready', {
|
||||
status: 'False',
|
||||
reason: 'MissingUrl',
|
||||
});
|
||||
}
|
||||
if (!token) {
|
||||
return await this.conditions.set('Ready', {
|
||||
status: 'False',
|
||||
reason: 'MissingToken',
|
||||
});
|
||||
}
|
||||
const values = {
|
||||
name,
|
||||
url,
|
||||
token,
|
||||
};
|
||||
const secretValue = decodeSecret(this.#secret.data);
|
||||
if (!deepEqual(secretValue, values)) {
|
||||
await this.#secret.patch({
|
||||
data: encodeSecret(values),
|
||||
});
|
||||
return await this.conditions.set('Ready', {
|
||||
status: 'False',
|
||||
reason: 'UpdatingSecret',
|
||||
});
|
||||
}
|
||||
|
||||
return await this.conditions.set('Ready', {
|
||||
status: 'True',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export { AuthentikConnectionResource };
|
||||
@@ -1,11 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { valueReferenceInfoSchema } from '../../services/value-reference/value-reference.instance.ts';
|
||||
|
||||
const authentikConnectionSpecSchema = z.object({
|
||||
name: valueReferenceInfoSchema,
|
||||
url: valueReferenceInfoSchema,
|
||||
token: valueReferenceInfoSchema,
|
||||
});
|
||||
|
||||
export { authentikConnectionSpecSchema };
|
||||
@@ -1,19 +0,0 @@
|
||||
import { createCustomResourceDefinition } from '../../services/custom-resources/custom-resources.ts';
|
||||
import { GROUP } from '../../utils/consts.ts';
|
||||
|
||||
import { AuthentikConnectionResource } from './authentik-connection.resource.ts';
|
||||
import { authentikConnectionSpecSchema } from './authentik-connection.schemas.ts';
|
||||
|
||||
const authentikConnectionDefinition = createCustomResourceDefinition({
|
||||
group: GROUP,
|
||||
version: 'v1',
|
||||
kind: 'AuthentikConnection',
|
||||
names: {
|
||||
plural: 'authentikconnections',
|
||||
singular: 'authentikconnection',
|
||||
},
|
||||
spec: authentikConnectionSpecSchema,
|
||||
create: (options) => new AuthentikConnectionResource(options),
|
||||
});
|
||||
|
||||
export { authentikConnectionDefinition };
|
||||
@@ -0,0 +1,211 @@
|
||||
import type { V1Secret } from '@kubernetes/client-node';
|
||||
|
||||
import { RepoService } from '../../bootstrap/repos/repos.ts';
|
||||
import { HelmReleaseInstance } from '../../instances/helm-release.ts';
|
||||
import { SecretInstance } from '../../instances/secret.ts';
|
||||
import {
|
||||
CustomResource,
|
||||
type CustomResourceOptions,
|
||||
type CustomResourceObject,
|
||||
} from '../../services/custom-resources/custom-resources.custom-resource.ts';
|
||||
import { ResourceReference } from '../../services/resources/resources.ref.ts';
|
||||
import { ResourceService } from '../../services/resources/resources.ts';
|
||||
import type { EnsuredSecret } from '../../services/secrets/secrets.secret.ts';
|
||||
import { SecretService } from '../../services/secrets/secrets.ts';
|
||||
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 { authentikServerInitSecretSchema, type authentikServerSpecSchema } from './authentik-server.schemas.ts';
|
||||
|
||||
class AuthentikServerController extends CustomResource<typeof authentikServerSpecSchema> {
|
||||
#environment: ResourceReference<CustomResourceObject<typeof environmentSpecSchema>>;
|
||||
#authentikInitSecret: EnsuredSecret<typeof authentikServerInitSecretSchema>;
|
||||
#authentikSecret: SecretInstance;
|
||||
#authentikRelease: HelmReleaseInstance;
|
||||
#postgresSecret: ResourceReference<V1Secret>;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof authentikServerSpecSchema>) {
|
||||
super(options);
|
||||
const secretService = this.services.get(SecretService);
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
|
||||
this.#environment = new ResourceReference();
|
||||
this.#authentikInitSecret = secretService.ensure({
|
||||
owner: [this.ref],
|
||||
name: `${this.name}-init`,
|
||||
namespace: this.namespace,
|
||||
schema: authentikServerInitSecretSchema,
|
||||
generator: () => ({
|
||||
AUTHENTIK_BOOTSTRAP_TOKEN: crypto.randomUUID(),
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD: crypto.randomUUID(),
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL: 'admin@example.com',
|
||||
AUTHENTIK_SECRET_KEY: crypto.randomUUID(),
|
||||
}),
|
||||
});
|
||||
this.#authentikSecret = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'Secret',
|
||||
name: `${this.name}-server`,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
SecretInstance,
|
||||
);
|
||||
this.#authentikRelease = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'helm.toolkit.fluxcd.io/v2',
|
||||
kind: 'HelmRelease',
|
||||
name: this.name,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
HelmReleaseInstance,
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
public reconcile = async () => {
|
||||
if (!this.exists || this.metadata?.deletionTimestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.#authentikInitSecret.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
const environmentNames = getWithNamespace(this.spec.environment, this.namespace);
|
||||
|
||||
this.#environment.current = resourceService.get({
|
||||
apiVersion: API_VERSION,
|
||||
kind: 'Environment',
|
||||
name: environmentNames.name,
|
||||
namespace: this.namespace,
|
||||
});
|
||||
|
||||
const postgresNames = getWithNamespace(this.spec.postgresCluster, this.namespace);
|
||||
this.#postgresSecret.current = resourceService.get({
|
||||
apiVersion: 'v1',
|
||||
kind: 'Secret',
|
||||
name: postgresNames.name,
|
||||
namespace: postgresNames.namespace,
|
||||
});
|
||||
|
||||
if (!this.#postgresSecret.current?.exists) {
|
||||
return;
|
||||
}
|
||||
const postgresSecret = decodeSecret(this.#postgresSecret.current.data) || {};
|
||||
|
||||
if (!this.#environment.current?.exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domain = this.#environment.current.spec?.domain;
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
const secretData = {
|
||||
url: `https://${this.spec.subdomain}.${domain}`,
|
||||
host: `${this.name}.${this.namespace}.svc.cluster.local`,
|
||||
token: this.#authentikInitSecret.value?.AUTHENTIK_BOOTSTRAP_TOKEN ?? '',
|
||||
};
|
||||
|
||||
await this.#authentikSecret.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
data: encodeSecret(secretData),
|
||||
});
|
||||
|
||||
const repoService = this.services.get(RepoService);
|
||||
|
||||
await this.#authentikRelease.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
interval: '60m',
|
||||
chart: {
|
||||
spec: {
|
||||
chart: 'authentik',
|
||||
version: '2025.6.4',
|
||||
sourceRef: {
|
||||
apiVersion: 'source.toolkit.fluxcd.io/v1',
|
||||
kind: 'HelmRepository',
|
||||
name: repoService.authentik.name,
|
||||
namespace: repoService.authentik.namespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
values: {
|
||||
global: {
|
||||
envFrom: [
|
||||
{
|
||||
secretRef: {
|
||||
name: this.#authentikInitSecret.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
authentik: {
|
||||
error_reporting: {
|
||||
enabled: false,
|
||||
},
|
||||
postgresql: {
|
||||
host: postgresSecret.host,
|
||||
name: postgresSecret.database,
|
||||
user: postgresSecret.username,
|
||||
password: 'file:///postgres-creds/password',
|
||||
},
|
||||
redis: {
|
||||
host: `redis.${this.namespace}.svc.cluster.local`,
|
||||
},
|
||||
},
|
||||
server: {
|
||||
volumes: [
|
||||
{
|
||||
name: 'postgres-creds',
|
||||
secret: {
|
||||
secretName: this.#postgresSecret.current.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'postgres-creds',
|
||||
mountPath: '/postgres-creds',
|
||||
readOnly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
worker: {
|
||||
volumes: [
|
||||
{
|
||||
name: 'postgres-creds',
|
||||
secret: {
|
||||
secretName: this.#postgresSecret.current.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'postgres-creds',
|
||||
mountPath: '/postgres-creds',
|
||||
readOnly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export { AuthentikServerController };
|
||||
@@ -0,0 +1,22 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const authentikServerSpecSchema = z.object({
|
||||
postgresCluster: z.string(),
|
||||
environment: z.string(),
|
||||
subdomain: z.string(),
|
||||
});
|
||||
|
||||
const authentikServerInitSecretSchema = z.object({
|
||||
AUTHENTIK_BOOTSTRAP_TOKEN: z.string(),
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD: z.string(),
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL: z.string(),
|
||||
AUTHENTIK_SECRET_KEY: z.string(),
|
||||
});
|
||||
|
||||
const authentikServerSecretSchema = z.object({
|
||||
url: z.string(),
|
||||
host: z.string(),
|
||||
token: z.string(),
|
||||
});
|
||||
|
||||
export { authentikServerSpecSchema, authentikServerInitSecretSchema, authentikServerSecretSchema };
|
||||
19
src/custom-resouces/authentik-server/authentik-server.ts
Normal file
19
src/custom-resouces/authentik-server/authentik-server.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createCustomResourceDefinition } from '../../services/custom-resources/custom-resources.ts';
|
||||
import { GROUP } from '../../utils/consts.ts';
|
||||
|
||||
import { authentikServerSpecSchema } from './authentik-server.schemas.ts';
|
||||
import { AuthentikServerController } from './authentik-server.controller.ts';
|
||||
|
||||
const authentikServerDefinition = createCustomResourceDefinition({
|
||||
group: GROUP,
|
||||
version: 'v1',
|
||||
kind: 'AuthentikServer',
|
||||
names: {
|
||||
plural: 'authentikservers',
|
||||
singular: 'authentikserver',
|
||||
},
|
||||
spec: authentikServerSpecSchema,
|
||||
create: (options) => new AuthentikServerController(options),
|
||||
});
|
||||
|
||||
export { authentikServerDefinition };
|
||||
@@ -1,13 +1,19 @@
|
||||
import { authentikClientDefinition } from './authentik-client/authentik-client.ts';
|
||||
import { authentikConnectionDefinition } from './authentik-connection/authentik-connection.ts';
|
||||
import { authentikServerDefinition } from './authentik-server/authentik-server.ts';
|
||||
import { environmentDefinition } from './environment/environment.ts';
|
||||
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';
|
||||
|
||||
const customResources = [
|
||||
postgresDatabaseDefinition,
|
||||
authentikClientDefinition,
|
||||
generateSecretDefinition,
|
||||
authentikConnectionDefinition,
|
||||
environmentDefinition,
|
||||
postgresClusterDefinition,
|
||||
authentikServerDefinition,
|
||||
httpServiceDefinition,
|
||||
];
|
||||
|
||||
export { customResources };
|
||||
|
||||
206
src/custom-resouces/environment/environment.controller.ts
Normal file
206
src/custom-resouces/environment/environment.controller.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { CertificateInstance } from '../../instances/certificate.ts';
|
||||
import { CustomDefinitionInstance } from '../../instances/custom-resource-definition.ts';
|
||||
import { NamespaceInstance } from '../../instances/namespace.ts';
|
||||
import {
|
||||
CustomResource,
|
||||
type CustomResourceOptions,
|
||||
} from '../../services/custom-resources/custom-resources.custom-resource.ts';
|
||||
import { ResourceService } from '../../services/resources/resources.ts';
|
||||
import { GatewayInstance } from '../../instances/gateway.ts';
|
||||
import { PostgresClusterInstance } from '../../instances/postgres-cluster.ts';
|
||||
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 type { environmentSpecSchema } from './environment.schemas.ts';
|
||||
|
||||
class EnvironmentController extends CustomResource<typeof environmentSpecSchema> {
|
||||
#namespace: NamespaceInstance;
|
||||
#certificateCrd: CustomDefinitionInstance;
|
||||
#certificate: CertificateInstance;
|
||||
#gatewayCrd: CustomDefinitionInstance;
|
||||
#gateway: GatewayInstance;
|
||||
#storageClass: StorageClassInstance;
|
||||
#postgresCluster: PostgresClusterInstance;
|
||||
#authentikServer: AuthentikServerInstance;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof environmentSpecSchema>) {
|
||||
super(options);
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
this.#namespace = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'Namespace',
|
||||
name: this.namespace,
|
||||
},
|
||||
NamespaceInstance,
|
||||
);
|
||||
this.#certificateCrd = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'apiextensions.k8s.io/v1',
|
||||
kind: 'CustomResourceDefinition',
|
||||
name: 'certificates.cert-manager.io',
|
||||
},
|
||||
CustomDefinitionInstance,
|
||||
);
|
||||
this.#certificate = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'cert-manager.io/v1',
|
||||
kind: 'Certificate',
|
||||
name: this.name,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
CertificateInstance,
|
||||
);
|
||||
this.#gatewayCrd = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'apiextensions.k8s.io/v1',
|
||||
kind: 'CustomResourceDefinition',
|
||||
name: 'gateways.networking.istio.io',
|
||||
},
|
||||
CustomDefinitionInstance,
|
||||
);
|
||||
this.#gateway = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'networking.istio.io/v1',
|
||||
kind: 'Gateway',
|
||||
name: this.name,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
GatewayInstance,
|
||||
);
|
||||
this.#storageClass = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'storage.k8s.io/v1',
|
||||
kind: 'StorageClass',
|
||||
name: `${this.name}-retain`,
|
||||
},
|
||||
StorageClassInstance,
|
||||
);
|
||||
this.#postgresCluster = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: API_VERSION,
|
||||
kind: 'PostgresCluster',
|
||||
name: `${this.name}-postgres-cluster`,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
PostgresClusterInstance,
|
||||
);
|
||||
this.#authentikServer = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: API_VERSION,
|
||||
kind: 'AuthentikServer',
|
||||
name: `${this.name}-authentik-server`,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
AuthentikServerInstance,
|
||||
);
|
||||
this.#gatewayCrd.on('changed', this.queueReconcile);
|
||||
this.#gateway.on('changed', this.queueReconcile);
|
||||
this.#certificateCrd.on('changed', this.queueReconcile);
|
||||
this.#namespace.on('changed', this.queueReconcile);
|
||||
this.#certificate.on('changed', this.queueReconcile);
|
||||
this.#postgresCluster.on('changed', this.queueReconcile);
|
||||
this.#authentikServer.on('changed', this.queueReconcile);
|
||||
this.#storageClass.on('changed', this.queueReconcile);
|
||||
}
|
||||
|
||||
public reconcile = async () => {
|
||||
if (!this.exists || this.metadata?.deletionTimestamp) {
|
||||
return;
|
||||
}
|
||||
await this.#namespace.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
labels: {
|
||||
'istio-injection': 'enabled',
|
||||
},
|
||||
},
|
||||
});
|
||||
if (this.#certificateCrd.ready) {
|
||||
await this.#certificate.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
secretName: `${this.name}-tls`,
|
||||
issuerRef: {
|
||||
name: 'cluster-issuer',
|
||||
kind: 'ClusterIssuer',
|
||||
},
|
||||
dnsNames: [`*.${this.spec.domain}`],
|
||||
privateKey: {
|
||||
rotationPolicy: 'Always',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (this.#gatewayCrd.ready) {
|
||||
await this.#gateway.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
selector: {
|
||||
istio: 'gateway',
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
hosts: [`*.${this.spec.domain}`],
|
||||
port: {
|
||||
name: 'http',
|
||||
number: 80,
|
||||
protocol: 'HTTP',
|
||||
},
|
||||
tls: {
|
||||
httpsRedirect: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
hosts: [`*.${this.spec.domain}`],
|
||||
port: {
|
||||
name: 'https',
|
||||
number: 443,
|
||||
protocol: 'HTTPS',
|
||||
},
|
||||
tls: {
|
||||
mode: 'SIMPLE',
|
||||
credentialName: `${this.name}-tls`,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
await this.#storageClass.ensure({
|
||||
provisioner: PROVISIONER,
|
||||
parameters: {
|
||||
storageLocation: this.spec.storage?.location || `/data/volumes/${this.name}`,
|
||||
reclaimPolicy: 'Retain',
|
||||
allowVolumeExpansion: 'true',
|
||||
volumeBindingMode: 'Immediate',
|
||||
},
|
||||
});
|
||||
await this.#postgresCluster.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
environment: this.name,
|
||||
},
|
||||
});
|
||||
await this.#authentikServer.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
environment: `${this.namespace}/${this.name}`,
|
||||
subdomain: 'authentik',
|
||||
postgresCluster: `${this.name}-postgres-cluster`,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { EnvironmentController };
|
||||
14
src/custom-resouces/environment/environment.schemas.ts
Normal file
14
src/custom-resouces/environment/environment.schemas.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const environmentSpecSchema = z.object({
|
||||
domain: z.string(),
|
||||
storage: z
|
||||
.object({
|
||||
location: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
type EnvironmentSpec = z.infer<typeof environmentSpecSchema>;
|
||||
|
||||
export { environmentSpecSchema, type EnvironmentSpec };
|
||||
19
src/custom-resouces/environment/environment.ts
Normal file
19
src/custom-resouces/environment/environment.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createCustomResourceDefinition } from '../../services/custom-resources/custom-resources.ts';
|
||||
import { GROUP } from '../../utils/consts.ts';
|
||||
|
||||
import { EnvironmentController } from './environment.controller.ts';
|
||||
import { environmentSpecSchema } from './environment.schemas.ts';
|
||||
|
||||
const environmentDefinition = createCustomResourceDefinition({
|
||||
group: GROUP,
|
||||
version: 'v1',
|
||||
kind: 'Environment',
|
||||
names: {
|
||||
plural: 'environments',
|
||||
singular: 'environment',
|
||||
},
|
||||
spec: environmentSpecSchema,
|
||||
create: (options) => new EnvironmentController(options),
|
||||
});
|
||||
|
||||
export { environmentDefinition };
|
||||
100
src/custom-resouces/http-service/http-service.controller.ts
Normal file
100
src/custom-resouces/http-service/http-service.controller.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { DestinationRuleInstance } from '../../instances/destination-rule.ts';
|
||||
import { VirtualServiceInstance } from '../../instances/virtual-service.ts';
|
||||
import {
|
||||
CustomResource,
|
||||
type CustomResourceObject,
|
||||
type CustomResourceOptions,
|
||||
} from '../../services/custom-resources/custom-resources.custom-resource.ts';
|
||||
import { ResourceReference, ResourceService } from '../../services/resources/resources.ts';
|
||||
import { API_VERSION } from '../../utils/consts.ts';
|
||||
import { getWithNamespace } from '../../utils/naming.ts';
|
||||
import { environmentSpecSchema } from '../environment/environment.schemas.ts';
|
||||
|
||||
import { httpServiceSpecSchema } from './http-service.schemas.ts';
|
||||
|
||||
class HttpServiceController extends CustomResource<typeof httpServiceSpecSchema> {
|
||||
#environment: ResourceReference<CustomResourceObject<typeof environmentSpecSchema>>;
|
||||
#virtualService: VirtualServiceInstance;
|
||||
#destinationRule: DestinationRuleInstance;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof httpServiceSpecSchema>) {
|
||||
super(options);
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
this.#environment = new ResourceReference();
|
||||
this.#virtualService = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'networking.istio.io/v1beta1',
|
||||
kind: 'VirtualService',
|
||||
name: `${this.name}-virtual-service`,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
VirtualServiceInstance,
|
||||
);
|
||||
this.#destinationRule = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'networking.istio.io/v1beta1',
|
||||
kind: 'DestinationRule',
|
||||
name: `${this.name}-destination-rule`,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
DestinationRuleInstance,
|
||||
);
|
||||
this.#destinationRule.on('changed', this.queueReconcile);
|
||||
this.#virtualService.on('changed', this.queueReconcile);
|
||||
this.#environment.on('changed', this.queueReconcile);
|
||||
}
|
||||
|
||||
public reconcile = async () => {
|
||||
if (!this.exists || this.metadata?.deletionTimestamp) {
|
||||
return;
|
||||
}
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
const environmentNames = getWithNamespace(this.spec.environment, this.namespace);
|
||||
this.#environment.current = resourceService.get({
|
||||
apiVersion: API_VERSION,
|
||||
kind: 'Environment',
|
||||
name: environmentNames.name,
|
||||
namespace: environmentNames.namespace,
|
||||
});
|
||||
const environment = this.#environment.current;
|
||||
if (!environment?.exists) {
|
||||
return;
|
||||
}
|
||||
await this.#virtualService.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
hosts: [`${this.spec.subdomain}.${environment.spec?.domain}`],
|
||||
gateways: [`${this.#environment.current.namespace}/gateway`],
|
||||
http: [
|
||||
{
|
||||
route: [
|
||||
{
|
||||
destination: {
|
||||
host: this.spec.destination.host,
|
||||
port: this.spec.destination.port,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
await this.#destinationRule.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
host: this.spec.destination.host,
|
||||
trafficPolicy: {
|
||||
tls: {
|
||||
mode: 'DISABLE',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export { HttpServiceController };
|
||||
18
src/custom-resouces/http-service/http-service.schemas.ts
Normal file
18
src/custom-resouces/http-service/http-service.schemas.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const httpServiceSpecSchema = z.object({
|
||||
environment: z.string(),
|
||||
subdomain: z.string(),
|
||||
destination: z.object({
|
||||
host: z.string(),
|
||||
port: z
|
||||
.object({
|
||||
number: z.number().optional(),
|
||||
protocol: z.enum(['http', 'https']).optional(),
|
||||
name: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export { httpServiceSpecSchema };
|
||||
19
src/custom-resouces/http-service/http-service.ts
Normal file
19
src/custom-resouces/http-service/http-service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createCustomResourceDefinition } from '../../services/custom-resources/custom-resources.ts';
|
||||
import { GROUP } from '../../utils/consts.ts';
|
||||
|
||||
import { HttpServiceController } from './http-service.controller.ts';
|
||||
import { httpServiceSpecSchema } from './http-service.schemas.ts';
|
||||
|
||||
const httpServiceDefinition = createCustomResourceDefinition({
|
||||
group: GROUP,
|
||||
version: 'v1',
|
||||
kind: 'HttpService',
|
||||
names: {
|
||||
plural: 'httpservices',
|
||||
singular: 'httpservice',
|
||||
},
|
||||
spec: httpServiceSpecSchema,
|
||||
create: (options) => new HttpServiceController(options),
|
||||
});
|
||||
|
||||
export { httpServiceDefinition };
|
||||
@@ -0,0 +1,155 @@
|
||||
import { ServiceInstance } from '../../instances/service.ts';
|
||||
import { StatefulSetInstance } from '../../instances/stateful-set.ts';
|
||||
import { ResourceService } from '../../services/resources/resources.ts';
|
||||
import {
|
||||
CustomResource,
|
||||
type CustomResourceOptions,
|
||||
} from '../../services/custom-resources/custom-resources.custom-resource.ts';
|
||||
import type { EnsuredSecret } from '../../services/secrets/secrets.secret.ts';
|
||||
import { SecretService } from '../../services/secrets/secrets.ts';
|
||||
|
||||
import { postgresClusterSecretSchema, type postgresClusterSpecSchema } from './postgres-cluster.schemas.ts';
|
||||
|
||||
class PostgresClusterController extends CustomResource<typeof postgresClusterSpecSchema> {
|
||||
#statefulSet: StatefulSetInstance;
|
||||
#headlessService: ServiceInstance;
|
||||
#service: ServiceInstance;
|
||||
#secret: EnsuredSecret<typeof postgresClusterSecretSchema>;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof postgresClusterSpecSchema>) {
|
||||
super(options);
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
const secretService = this.services.get(SecretService);
|
||||
this.#statefulSet = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'apps/v1',
|
||||
kind: 'StatefulSet',
|
||||
name: this.name,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
StatefulSetInstance,
|
||||
);
|
||||
this.#headlessService = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'Service',
|
||||
name: `${this.name}-headless`,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
ServiceInstance,
|
||||
);
|
||||
this.#service = resourceService.getInstance(
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'Service',
|
||||
name: this.name,
|
||||
namespace: this.namespace,
|
||||
},
|
||||
ServiceInstance,
|
||||
);
|
||||
this.#secret = secretService.ensure({
|
||||
name: this.name,
|
||||
namespace: this.namespace,
|
||||
schema: postgresClusterSecretSchema,
|
||||
generator: () => {
|
||||
return {
|
||||
database: 'postgres',
|
||||
host: `${this.name}.${this.namespace}.svc.cluster.local`,
|
||||
port: '5432',
|
||||
username: 'postgres',
|
||||
password: crypto.randomUUID(),
|
||||
};
|
||||
},
|
||||
});
|
||||
this.#statefulSet.on('changed', this.queueReconcile);
|
||||
this.#service.on('changed', this.queueReconcile);
|
||||
this.#headlessService.on('changed', this.queueReconcile);
|
||||
this.#secret.resource.on('changed', this.queueReconcile);
|
||||
}
|
||||
|
||||
public reconcile = async () => {
|
||||
if (!this.exists || this.metadata?.deletionTimestamp || !this.#secret.isValid) {
|
||||
return;
|
||||
}
|
||||
await this.#headlessService.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
clusterIP: 'None',
|
||||
selector: {
|
||||
app: this.name,
|
||||
},
|
||||
ports: [{ name: 'postgres', port: 5432, targetPort: 5432 }],
|
||||
},
|
||||
});
|
||||
await this.#statefulSet.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
replicas: 1,
|
||||
serviceName: this.name,
|
||||
selector: {
|
||||
matchLabels: {
|
||||
app: this.name,
|
||||
},
|
||||
},
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: this.name,
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: this.name,
|
||||
image: 'postgres:17',
|
||||
ports: [{ containerPort: 5432, name: 'postgres' }],
|
||||
env: [
|
||||
{ name: 'POSTGRES_PASSWORD', valueFrom: { secretKeyRef: { name: this.name, key: 'password' } } },
|
||||
{ name: 'POSTGRES_USER', valueFrom: { secretKeyRef: { name: this.name, key: 'username' } } },
|
||||
{ name: 'POSTGRES_DB', value: this.name },
|
||||
{ name: 'PGDATA', value: '/var/lib/postgresql/data/pgdata' },
|
||||
],
|
||||
volumeMounts: [{ name: this.name, mountPath: '/var/lib/postgresql/data' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
volumeClaimTemplates: [
|
||||
{
|
||||
metadata: {
|
||||
name: this.name,
|
||||
},
|
||||
spec: {
|
||||
accessModes: ['ReadWriteOnce'],
|
||||
storageClassName: `${this.spec.environment}-retain`,
|
||||
resources: {
|
||||
requests: {
|
||||
storage: this.spec.storage?.size || '1Gi',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await this.#service.ensure({
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
spec: {
|
||||
type: 'ClusterIP',
|
||||
selector: {
|
||||
app: this.name,
|
||||
},
|
||||
ports: [{ name: 'postgres', port: 5432, targetPort: 5432 }],
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export { PostgresClusterController };
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const postgresClusterSpecSchema = z.object({
|
||||
environment: z.string(),
|
||||
storage: z
|
||||
.object({
|
||||
size: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const postgresClusterSecretSchema = z.object({
|
||||
database: z.string(),
|
||||
host: z.string(),
|
||||
port: z.string(),
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
export { postgresClusterSpecSchema, postgresClusterSecretSchema };
|
||||
19
src/custom-resouces/postgres-cluster/postgres-cluster.ts
Normal file
19
src/custom-resouces/postgres-cluster/postgres-cluster.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createCustomResourceDefinition } from '../../services/custom-resources/custom-resources.ts';
|
||||
import { GROUP } from '../../utils/consts.ts';
|
||||
|
||||
import { PostgresClusterController } from './postgres-cluster.controller.ts';
|
||||
import { postgresClusterSpecSchema } from './postgres-cluster.schemas.ts';
|
||||
|
||||
const postgresClusterDefinition = createCustomResourceDefinition({
|
||||
group: GROUP,
|
||||
version: 'v1',
|
||||
kind: 'PostgresCluster',
|
||||
names: {
|
||||
plural: 'postgres-clusters',
|
||||
singular: 'postgres-cluster',
|
||||
},
|
||||
spec: postgresClusterSpecSchema,
|
||||
create: (options) => new PostgresClusterController(options),
|
||||
});
|
||||
|
||||
export { postgresClusterDefinition };
|
||||
@@ -1,23 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const postgresDatabaseSpecSchema = z.object({
|
||||
secretRef: z.string(),
|
||||
cluster: z.string(),
|
||||
});
|
||||
|
||||
const postgresDatabaseSecretSchema = z.object({
|
||||
host: z.string(),
|
||||
port: z.string(),
|
||||
user: z.string(),
|
||||
password: z.string(),
|
||||
database: z.string().optional(),
|
||||
});
|
||||
|
||||
const postgresDatabaseConnectionSecretSchema = z.object({
|
||||
host: z.string(),
|
||||
port: z.string(),
|
||||
user: z.string(),
|
||||
password: z.string(),
|
||||
database: z.string(),
|
||||
});
|
||||
|
||||
export { postgresDatabaseSpecSchema, postgresDatabaseSecretSchema, postgresDatabaseConnectionSecretSchema };
|
||||
export { postgresDatabaseSpecSchema };
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { z } from 'zod';
|
||||
import type { V1Secret } from '@kubernetes/client-node';
|
||||
|
||||
import {
|
||||
@@ -12,42 +11,31 @@ import { Resource, ResourceService } from '../../services/resources/resources.ts
|
||||
import { getWithNamespace } from '../../utils/naming.ts';
|
||||
import { decodeSecret, encodeSecret } from '../../utils/secrets.ts';
|
||||
import { isDeepSubset } from '../../utils/objects.ts';
|
||||
import { postgresClusterSecretSchema } from '../postgres-cluster/postgres-cluster.schemas.ts';
|
||||
|
||||
import {
|
||||
postgresDatabaseConnectionSecretSchema,
|
||||
postgresDatabaseSecretSchema,
|
||||
type postgresDatabaseSpecSchema,
|
||||
} from './portgres-database.schemas.ts';
|
||||
import { type postgresDatabaseSpecSchema } from './portgres-database.schemas.ts';
|
||||
|
||||
const SECRET_READY_CONDITION = 'Secret';
|
||||
const DATABASE_READY_CONDITION = 'Database';
|
||||
|
||||
const secretDataSchema = z.object({
|
||||
host: z.string(),
|
||||
port: z.string().optional(),
|
||||
database: z.string(),
|
||||
user: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpecSchema> {
|
||||
#serverSecret: ResourceReference<V1Secret>;
|
||||
#clusterSecret: ResourceReference<V1Secret>;
|
||||
#databaseSecret: Resource<V1Secret>;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof postgresDatabaseSpecSchema>) {
|
||||
super(options);
|
||||
this.#serverSecret = new ResourceReference();
|
||||
this.#clusterSecret = new ResourceReference();
|
||||
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
this.#databaseSecret = resourceService.get({
|
||||
apiVersion: 'v1',
|
||||
kind: 'Secret',
|
||||
name: `${this.name}-connection`,
|
||||
name: `${this.name}-postgres-database`,
|
||||
namespace: this.namespace,
|
||||
});
|
||||
|
||||
this.#updateSecret();
|
||||
this.#serverSecret.on('changed', this.queueReconcile);
|
||||
this.#clusterSecret.on('changed', this.queueReconcile);
|
||||
}
|
||||
|
||||
get #dbName() {
|
||||
@@ -60,17 +48,17 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
|
||||
#updateSecret = () => {
|
||||
const resourceService = this.services.get(ResourceService);
|
||||
const secretNames = getWithNamespace(this.spec.secretRef, this.namespace);
|
||||
this.#serverSecret.current = resourceService.get({
|
||||
const secretNames = getWithNamespace(this.spec.cluster, this.namespace);
|
||||
this.#clusterSecret.current = resourceService.get({
|
||||
apiVersion: 'v1',
|
||||
kind: 'Secret',
|
||||
name: secretNames.name,
|
||||
name: `${secretNames.name}-postgres-cluster`,
|
||||
namespace: secretNames.namespace,
|
||||
});
|
||||
};
|
||||
|
||||
#reconcileSecret = async (): Promise<SubresourceResult> => {
|
||||
const serverSecret = this.#serverSecret.current;
|
||||
const serverSecret = this.#clusterSecret.current;
|
||||
const databaseSecret = this.#databaseSecret;
|
||||
|
||||
if (!serverSecret?.exists || !serverSecret.data) {
|
||||
@@ -80,7 +68,7 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
reason: 'MissingConnectionSecret',
|
||||
};
|
||||
}
|
||||
const serverSecretData = postgresDatabaseSecretSchema.safeParse(decodeSecret(serverSecret.data));
|
||||
const serverSecretData = postgresClusterSecretSchema.safeParse(decodeSecret(serverSecret.data));
|
||||
if (!serverSecretData.success || !serverSecretData.data) {
|
||||
return {
|
||||
ready: false,
|
||||
@@ -88,7 +76,7 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
reason: 'SecretMissing',
|
||||
};
|
||||
}
|
||||
const databaseSecretData = postgresDatabaseConnectionSecretSchema.safeParse(decodeSecret(databaseSecret.data));
|
||||
const databaseSecretData = postgresClusterSecretSchema.safeParse(decodeSecret(databaseSecret.data));
|
||||
const expectedSecret = {
|
||||
password: crypto.randomUUID(),
|
||||
host: serverSecretData.data.host,
|
||||
@@ -115,8 +103,8 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
};
|
||||
|
||||
#reconcileDatabase = async (): Promise<SubresourceResult> => {
|
||||
const connectionSecret = this.#serverSecret.current;
|
||||
if (!connectionSecret?.exists || !connectionSecret.data) {
|
||||
const clusterSecret = this.#clusterSecret.current;
|
||||
if (!clusterSecret?.exists || !clusterSecret.data) {
|
||||
return {
|
||||
ready: false,
|
||||
failed: true,
|
||||
@@ -124,7 +112,7 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
};
|
||||
}
|
||||
|
||||
const connectionSecretData = postgresDatabaseSecretSchema.safeParse(decodeSecret(connectionSecret.data));
|
||||
const connectionSecretData = postgresClusterSecretSchema.safeParse(decodeSecret(clusterSecret.data));
|
||||
if (!connectionSecretData.success || !connectionSecretData.data) {
|
||||
return {
|
||||
ready: false,
|
||||
@@ -133,7 +121,7 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
};
|
||||
}
|
||||
|
||||
const secretData = postgresDatabaseConnectionSecretSchema.safeParse(decodeSecret(this.#databaseSecret.data));
|
||||
const secretData = postgresClusterSecretSchema.safeParse(decodeSecret(this.#databaseSecret.data));
|
||||
if (!secretData.success || !secretData.data) {
|
||||
return {
|
||||
ready: false,
|
||||
@@ -149,12 +137,12 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
database: connectionSecretData.data.database,
|
||||
});
|
||||
await database.upsertRole({
|
||||
name: secretData.data.user,
|
||||
name: secretData.data.username,
|
||||
password: secretData.data.password,
|
||||
});
|
||||
await database.upsertDatabase({
|
||||
name: secretData.data.database,
|
||||
owner: secretData.data.user,
|
||||
owner: secretData.data.username,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -180,4 +168,4 @@ class PostgresDatabaseResource extends CustomResource<typeof postgresDatabaseSpe
|
||||
};
|
||||
}
|
||||
|
||||
export { PostgresDatabaseResource, secretDataSchema as postgresDatabaseSecretSchema };
|
||||
export { PostgresDatabaseResource };
|
||||
|
||||
Reference in New Issue
Block a user