From dfeab2255e3187bb5e5f06be7259043d56f660dd Mon Sep 17 00:00:00 2001 From: Daniel Noulet Date: Sat, 3 Oct 2026 01:20:14 +0200 Subject: [PATCH] feat: multi tenant support --- deploy/helm/go-guess/Chart.yaml | 4 +- deploy/helm/go-guess/templates/NOTES.txt | 6 +- .../templates/postgresql-statefulset.yaml | 4 +- .../templates/rabbitmq-statefulset.yaml | 2 +- deploy/helm/go-guess/values.yaml | 10 + dist/go-guess-0.1.0.tgz | Bin 0 -> 4463 bytes docker-compose.yml | 2 + go-guess.yaml | 418 ++++++++++++++++++ 8 files changed, 439 insertions(+), 7 deletions(-) create mode 100644 dist/go-guess-0.1.0.tgz create mode 100644 go-guess.yaml diff --git a/deploy/helm/go-guess/Chart.yaml b/deploy/helm/go-guess/Chart.yaml index 22e298e..7d5d697 100644 --- a/deploy/helm/go-guess/Chart.yaml +++ b/deploy/helm/go-guess/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: go-guess description: Go Guess recruitment workspace type: application -version: 0.1.0 -appVersion: "0.1.0" +version: 0.2.0 +appVersion: "0.2.0" diff --git a/deploy/helm/go-guess/templates/NOTES.txt b/deploy/helm/go-guess/templates/NOTES.txt index eef12fe..5b2ffba 100644 --- a/deploy/helm/go-guess/templates/NOTES.txt +++ b/deploy/helm/go-guess/templates/NOTES.txt @@ -1,5 +1,9 @@ Go Guess has been installed in namespace {{ .Release.Namespace }}. -Protected interviewer routes authorize with Go Loose at {{ .Values.app.goLooseBaseURL }}. +{{- if .Values.goLoose.enabled }} +Browser sign-in uses Go Loose at https://.{{ .Values.goLoose.authDomain }}. +{{- else }} +Browser sign-in uses the application password login. Enable goLoose to redirect tenant hosts. +{{- end }} {{- if .Values.ingress.enabled }} Open {{ .Values.app.frontendURL }} diff --git a/deploy/helm/go-guess/templates/postgresql-statefulset.yaml b/deploy/helm/go-guess/templates/postgresql-statefulset.yaml index 376629d..1f3d862 100644 --- a/deploy/helm/go-guess/templates/postgresql-statefulset.yaml +++ b/deploy/helm/go-guess/templates/postgresql-statefulset.yaml @@ -44,8 +44,6 @@ spec: secretKeyRef: name: {{ include "go-guess.secretName" . }} key: postgresql-password - - name: PGDATA - value: /var/lib/postgresql/data/pgdata ports: - name: postgresql containerPort: 5432 @@ -69,7 +67,7 @@ spec: {{- toYaml .Values.postgresql.resources | nindent 12 }} volumeMounts: - name: data - mountPath: /var/lib/postgresql/data + mountPath: /var/lib/postgresql {{- if not .Values.postgresql.persistence.enabled }} volumes: - name: data diff --git a/deploy/helm/go-guess/templates/rabbitmq-statefulset.yaml b/deploy/helm/go-guess/templates/rabbitmq-statefulset.yaml index 52b4716..57d6ce5 100644 --- a/deploy/helm/go-guess/templates/rabbitmq-statefulset.yaml +++ b/deploy/helm/go-guess/templates/rabbitmq-statefulset.yaml @@ -21,7 +21,7 @@ spec: spec: automountServiceAccountToken: false securityContext: - fsGroup: 100 + fsGroup: 101 fsGroupChangePolicy: OnRootMismatch containers: - name: rabbitmq diff --git a/deploy/helm/go-guess/values.yaml b/deploy/helm/go-guess/values.yaml index 6537aa2..cc268de 100644 --- a/deploy/helm/go-guess/values.yaml +++ b/deploy/helm/go-guess/values.yaml @@ -16,6 +16,16 @@ app: tokenTTL: 8h maxUploadMB: 10 +goLoose: + enabled: false + authDomain: auth.dev + appDomain: guess.dev + ca: + existingSecret: "" + key: ca.crt + mountPath: /certs/local-ca.crt + tenants: [] + secrets: jwtSecret: "" databaseURL: "" diff --git a/dist/go-guess-0.1.0.tgz b/dist/go-guess-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f45968a0aeb3fca00a12e21c7d46cf118596bf06 GIT binary patch literal 4463 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 B#f|^~ literal 0 HcmV?d00001 diff --git a/docker-compose.yml b/docker-compose.yml index 78f3869..72ba79f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,7 @@ services: api: build: ./backend + image: batty1039.startdedicated.net:5000/go-guess-api:0.0.1 environment: DATABASE_URL: postgres://go_guess:go_guess@db:5432/go_guess?sslmode=disable JWT_SECRET: local-development-secret-change-me-12345 @@ -60,6 +61,7 @@ services: web: build: ./frontend + image: batty1039.startdedicated.net:5000/go-guess-web:0.0.1 expose: - "80" depends_on: diff --git a/go-guess.yaml b/go-guess.yaml new file mode 100644 index 0000000..289581e --- /dev/null +++ b/go-guess.yaml @@ -0,0 +1,418 @@ +--- +# Source: go-guess/templates/secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: go-guess-secrets + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + jwt-secret: "WHkwYjF1c3oxRlU5aU1yRTQ1MEFBdmZSazVCdnJYVm5EajJyT3pHQmx3bmFmR21U" + database-url: "cG9zdGdyZXM6Ly9nb19ndWVzczpjVUFDQVRHRXc2c2p5OVNpd1pLVEg2ZW83eXI5V1dtMUBnby1ndWVzcy1wb3N0Z3Jlc3FsOjU0MzIvZ29fZ3Vlc3M/c3NsbW9kZT1kaXNhYmxl" + rabbitmq-url: "YW1xcDovL2dvX2d1ZXNzOmMxUWhKOEdEZ3ZXdzR5ZlM3UVZIa3BFd21LOGFGSnY2QGdvLWd1ZXNzLXJhYmJpdG1xOjU2NzIv" + postgresql-password: "Y1VBQ0FUR0V3NnNqeTlTaXdaS1RINmVvN3lyOVdXbTE=" + rabbitmq-password: "YzFRaEo4R0Rndld3NHlmUzdRVkhrcEV3bUs4YUZKdjY=" + +--- +# Source: go-guess/templates/app-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: go-guess + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: application + ports: + - name: http + port: 80 + targetPort: http + protocol: TCP + +--- +# Source: go-guess/templates/postgresql-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: go-guess-postgresql + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: postgresql +spec: + clusterIP: None + selector: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: postgresql + ports: + - name: postgresql + port: 5432 + targetPort: postgresql + +--- +# Source: go-guess/templates/rabbitmq-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: go-guess-rabbitmq + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: rabbitmq +spec: + clusterIP: None + selector: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: rabbitmq + ports: + - name: amqp + port: 5672 + targetPort: amqp + +--- +# Source: go-guess/templates/app-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: go-guess + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: application + template: + metadata: + annotations: + checksum/secrets: 35d2fb3ab7972008630bcb7d33a419558d26ded80acf4454d32d2a3fcc82d721 + labels: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: application + spec: + automountServiceAccountToken: false + securityContext: + fsGroup: 10001 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: go-guess + image: "batty1039.startdedicated.net:5000/go-guess-api:0.0.1" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 10001 + env: + - name: APP_ENV + value: "production" + - name: FRONTEND_URL + value: "http://localhost:8080" + - name: TOKEN_TTL + value: "8h" + - name: MAX_UPLOAD_MB + value: "10" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: go-guess-secrets + key: database-url + - name: RABBITMQ_URL + valueFrom: + secretKeyRef: + name: go-guess-secrets + key: rabbitmq-url + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: go-guess-secrets + key: jwt-secret + ports: + - name: http + containerPort: 8080 + protocol: TCP + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 12 + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + volumeMounts: + - name: temporary-files + mountPath: /tmp + volumes: + - name: temporary-files + emptyDir: {} + +--- +# Source: go-guess/templates/postgresql-statefulset.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: go-guess-postgresql + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: postgresql +spec: + serviceName: go-guess-postgresql + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: postgresql + template: + metadata: + labels: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: postgresql + spec: + automountServiceAccountToken: false + securityContext: + fsGroup: 70 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: postgresql + image: "postgres:18-alpine" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + runAsUser: 70 + env: + - name: POSTGRES_DB + value: "go_guess" + - name: POSTGRES_USER + value: "go_guess" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: go-guess-secrets + key: postgresql-password + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + ports: + - name: postgresql + containerPort: 5432 + readinessProbe: + exec: + command: + - sh + - -ec + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + exec: + command: + - sh + - -ec + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 20 + periodSeconds: 10 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi + +--- +# Source: go-guess/templates/rabbitmq-statefulset.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: go-guess-rabbitmq + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: rabbitmq +spec: + serviceName: go-guess-rabbitmq + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: rabbitmq + template: + metadata: + labels: + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/component: rabbitmq + spec: + automountServiceAccountToken: false + securityContext: + fsGroup: 100 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: rabbitmq + image: "rabbitmq:4-alpine" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + runAsUser: 100 + env: + - name: RABBITMQ_DEFAULT_USER + value: "go_guess" + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: go-guess-secrets + key: rabbitmq-password + ports: + - name: amqp + containerPort: 5672 + readinessProbe: + exec: + command: + - rabbitmq-diagnostics + - -q + - ping + periodSeconds: 10 + timeoutSeconds: 10 + failureThreshold: 6 + startupProbe: + exec: + command: + - rabbitmq-diagnostics + - -q + - ping + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 10 + failureThreshold: 30 + livenessProbe: + exec: + command: + - rabbitmq-diagnostics + - -q + - ping + periodSeconds: 20 + timeoutSeconds: 10 + failureThreshold: 3 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + volumeMounts: + - name: data + mountPath: /var/lib/rabbitmq + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi +--- +# Source: go-guess/templates/tests/test-connection.yaml +apiVersion: v1 +kind: Pod +metadata: + name: "go-guess-test-connection" + labels: + helm.sh/chart: go-guess-0.1.0 + app.kubernetes.io/name: go-guess + app.kubernetes.io/instance: go-guess + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation +spec: + automountServiceAccountToken: false + restartPolicy: Never + containers: + - name: wget + image: busybox:1.37 + command: + - wget + args: + - -qO- + - "http://go-guess:80/api/health" +