mirror of
https://github.com/morten-olsen/homelab-operator.git
synced 2026-02-08 01:36:28 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12c74dadb8 | ||
|
|
2add15d283 | ||
|
|
5426495be5 |
16
.github/workflows/main.yml
vendored
16
.github/workflows/main.yml
vendored
@@ -71,9 +71,23 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
- id: create-release
|
||||
uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
config-name: release-drafter-config.yml
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: ./operator.yaml
|
||||
asset_name: operator.yaml
|
||||
asset_content_type: application/yaml
|
||||
@@ -9,4 +9,6 @@ metadata:
|
||||
name: dev
|
||||
namespace: dev
|
||||
spec:
|
||||
domain: dev.mortenolsen.pro
|
||||
domain: one.dev.olsen.cloud
|
||||
tls:
|
||||
issuer: letsencrypt-prod
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: homelab-operator-local-path
|
||||
provisioner: homelab-operator-local-path
|
||||
reclaimPolicy: Retain
|
||||
allowVolumeExpansion: true
|
||||
volumeBindingMode: Immediate
|
||||
parameters:
|
||||
hello: 'world'
|
||||
34
operator.yaml
Normal file
34
operator.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: homelab
|
||||
|
||||
---
|
||||
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: GitRepository
|
||||
metadata:
|
||||
name: homelab
|
||||
namespace: homelab
|
||||
spec:
|
||||
interval: 60m
|
||||
url: https://github.com/morten-olsen/homelab-operator
|
||||
ref:
|
||||
branch: main
|
||||
|
||||
---
|
||||
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: operator
|
||||
namespace: homelab
|
||||
spec:
|
||||
releaseName: operator
|
||||
chart:
|
||||
spec:
|
||||
chart: chart
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: homelab
|
||||
namespace: homelab
|
||||
@@ -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: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const authentikServerSpecSchema = z.object({
|
||||
redisServer: z.string(),
|
||||
postgresCluster: z.string(),
|
||||
environment: z.string(),
|
||||
subdomain: z.string(),
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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: {},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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: [
|
||||
|
||||
82
src/custom-resouces/redis-server/redis-server.controller.ts
Normal file
82
src/custom-resouces/redis-server/redis-server.controller.ts
Normal 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 };
|
||||
5
src/custom-resouces/redis-server/redis-server.schemas.ts
Normal file
5
src/custom-resouces/redis-server/redis-server.schemas.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const redisServerSpecSchema = z.object({});
|
||||
|
||||
export { redisServerSpecSchema };
|
||||
19
src/custom-resouces/redis-server/redis-server.ts
Normal file
19
src/custom-resouces/redis-server/redis-server.ts
Normal 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 };
|
||||
7
src/instances/http-service.ts
Normal file
7
src/instances/http-service.ts
Normal 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 };
|
||||
7
src/instances/redis-server.ts
Normal file
7
src/instances/redis-server.ts
Normal 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 };
|
||||
Reference in New Issue
Block a user