diff --git a/.github/workflows/ci-deploy.yml b/.github/workflows/ci-deploy.yml index 65875fb4b..3f2d3e51d 100644 --- a/.github/workflows/ci-deploy.yml +++ b/.github/workflows/ci-deploy.yml @@ -17,7 +17,7 @@ jobs: needs: build uses: ./.github/workflows/_deploy.yml with: - image_tag: ${{ needs.build.outputs.image_tag }} + image_tag: '${{ needs.build.outputs.image_tag }}@${{ needs.build.outputs.image_digest }}' deployment_name: vela environment: ${{ github.ref_name == 'main' && 'prod' || 'dev' }} secrets: inherit diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 6c07a864e..5e25eca9a 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -3,16 +3,37 @@ Reusable snippets shared across Vela Helm templates. */}} {{/* -Renders a Postgres init container that waits for the target database to accept connections. +Returns the plaintext password for a given DB credential key, preserving it across upgrades +via lookup of the existing vela-controller-secret. On fresh installs the password is derived +deterministically from the release name, namespace, and key so that all templates in the same +render produce the same value. + +Usage: {{ include "vela.dbPassword" (list "controller-db-password" .) }} +*/}} +{{- define "vela.dbPassword" -}} +{{- $key := index . 0 -}} +{{- $ctx := index . 1 -}} +{{- $existingSecret := lookup "v1" "Secret" $ctx.Release.Namespace "vela-controller-secret" -}} +{{- if and $existingSecret (index $existingSecret.data $key) -}} +{{- index $existingSecret.data $key | b64dec -}} +{{- else -}} +{{- printf "%s-%s-%s" $ctx.Release.Name $ctx.Release.Namespace $key | sha256sum | trunc 32 -}} +{{- end -}} +{{- end -}} + +{{/* +Renders a Postgres init container that waits for the server to be ready. +When `database` is provided it also waits until that specific database accepts connections. The helper accepts a dictionary with the following optional keys: - name : init container name (default: wait-for-database) - image : container image (default: postgres:17-alpine) - imagePullPolicy : pull policy (default: IfNotPresent) - host : database hostname (default: database) - port : database port (default: 5432) - - secretName : Kubernetes secret with credentials (default: database) - - usernameKey : Secret key used for DB username (default: superuser-username) - - passwordKey : Secret key used for DB password (default: superuser-password) + - database : if set, block until this database accepts connections + - secretName : secret containing credentials for the psql check (default: database) + - usernameKey : key for the DB username in secretName (default: superuser-username) + - passwordKey : key for the DB password in secretName (default: superuser-password) - securityContext : optional security context applied to the init container */}} {{- define "vela.waitForPostgresInitContainer" -}} @@ -21,6 +42,7 @@ The helper accepts a dictionary with the following optional keys: {{- $imagePullPolicy := default "IfNotPresent" .imagePullPolicy -}} {{- $host := default "database" .host -}} {{- $port := default "5432" .port -}} +{{- $database := .database -}} {{- $secretName := default "database" .secretName -}} {{- $usernameKey := default "superuser-username" .usernameKey -}} {{- $passwordKey := default "superuser-password" .passwordKey -}} @@ -32,6 +54,7 @@ The helper accepts a dictionary with the following optional keys: value: {{ $host | quote }} - name: DB_PORT value: {{ $port | quote }} +{{- if $database }} - name: DB_USER valueFrom: secretKeyRef: @@ -42,20 +65,22 @@ The helper accepts a dictionary with the following optional keys: secretKeyRef: name: {{ $secretName }} key: {{ $passwordKey }} +{{- end }} command: ["/bin/sh", "-c"] args: - | echo "Waiting for database..." - until pg_isready -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER"; do + until pg_isready -h "$DB_HOST" -p "$DB_PORT"; do sleep 2 done echo "Database is ready" +{{- if $database }} - # Ensure postgres user can connect - until psql -h "$DB_HOST" -U "$DB_USER" -d postgres -c '\q' 2>/dev/null; do - echo "Waiting for Postgres superuser connection..." + until psql -h "$DB_HOST" -U "$DB_USER" -d {{ $database | quote }} -c '\q' 2>/dev/null; do + echo "Waiting for Postgres connection to {{ $database }}..." sleep 2 done +{{- end }} {{- with .securityContext }} securityContext: {{ toYaml . | nindent 4 }} diff --git a/chart/templates/controller.yaml b/chart/templates/controller.yaml deleted file mode 100644 index 0021b8932..000000000 --- a/chart/templates/controller.yaml +++ /dev/null @@ -1,176 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: vela-controller-secret -type: Opaque -data: - {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace "vela-controller-secret" }} - {{- if $existingSecret }} - deployment-password-secret: {{ index $existingSecret.data "deployment-password-secret" }} - {{- else }} - deployment-password-secret: {{ randAlphaNum 32 | b64enc }} - {{- end }} ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vela-controller - namespace: {{ .Release.Namespace }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: vela-controller-{{ .Release.Namespace }}-admin -subjects: - - kind: ServiceAccount - name: vela-controller - namespace: {{ .Release.Namespace }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: vela-controller - labels: - app.kubernetes.io/name: vela-controller - app.kubernetes.io/instance: {{ .Release.Name }} -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: vela-controller - template: - metadata: - labels: - app.kubernetes.io/name: vela-controller - app.kubernetes.io/instance: {{ .Release.Name }} - spec: - serviceAccountName: vela-controller - initContainers: -{{ include "vela.waitForPostgresInitContainer" (dict) | nindent 8 }} - containers: - - name: vela-controller - image: "{{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag }}" - imagePullPolicy: Always - ports: - - name: http - containerPort: 8000 - protocol: TCP - envFrom: - - configMapRef: - name: vela-controller-config - env: - - name: VELA_GRAFANA_SECURITY_ADMIN_USER - valueFrom: - secretKeyRef: - name: vela-grafana-secret - key: VELA_GRAFANA_SECURITY_ADMIN_USER - - name: VELA_GRAFANA_SECURITY_ADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: vela-grafana-secret - key: VELA_GRAFANA_SECURITY_ADMIN_PASSWORD - - name: DB_USER - valueFrom: - secretKeyRef: - name: database - key: superuser-username - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: database - key: superuser-password - - name: VELA_DEPLOYMENT_PASSWORD_SECRET - valueFrom: - secretKeyRef: - name: vela-controller-secret - key: deployment-password-secret - - name: DB_HOST - value: database - - name: VELA_POSTGRES_URL - value: 'postgresql+asyncpg://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):5432/postgres' - - name: VELA_GRAFANA_URL - value: "https://{{ .Values.domain }}:{{ .Values.port }}/grafana" - livenessProbe: - httpGet: - path: /health - port: http - periodSeconds: 5 - timeoutSeconds: 5 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 10 - {{- with .Values.containerSecurityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.podSecurityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: vela-controller-config -data: - VELA_ROOT_PATH: {{ .Values.controller.env.VELA_ROOT_PATH | quote }} - VELA_JWT_SECRET: {{ .Values.controller.env.VELA_JWT_SECRET | quote }} - VELA_GATEWAY_NAME: {{ .Values.gateway.name | quote }} - VELA_GATEWAY_NAMESPACE: {{ .Values.gateway.namespace | quote }} - VELA_PGMETA_CRYPTO_KEY: {{ .Values.controller.env.VELA_PGMETA_CRYPTO_KEY | quote }} - VELA_CORS_ORIGINS: {{ .Values.controller.env.VELA_CORS_ORIGINS | quote }} - VELA_KEYCLOAK_URL: {{ .Values.controller.env.VELA_KEYCLOAK_URL | quote }} - VELA_KEYCLOAK_ADMIN_NAME: {{ .Values.controller.env.VELA_KEYCLOAK_ADMIN_NAME | quote }} - VELA_KEYCLOAK_ADMIN_SECRET: {{ .Values.controller.env.VELA_KEYCLOAK_ADMIN_SECRET | quote }} - VELA_CLOUDFLARE__API_TOKEN: {{ .Values.controller.env.VELA_CLOUDFLARE_API_TOKEN | quote }} - VELA_CLOUDFLARE__ZONE_ID: {{ .Values.controller.env.VELA_CLOUDFLARE_ZONE_ID | quote }} - VELA_CLOUDFLARE__BRANCH_REF: {{ .Values.controller.env.VELA_BRANCH_REF | quote }} - VELA_CLOUDFLARE__BRANCH_DB_REF: {{ .Values.controller.env.VELA_BRANCH_DB_REF | quote }} - VELA_CLOUDFLARE__DOMAIN_SUFFIX: {{ .Values.domainSuffix | default .Values.domain | quote }} - VELA_DEPLOYMENT_NAMESPACE_PREFIX: {{ .Values.controller.env.VELA_DEPLOYMENT_NAMESPACE_PREFIX | quote }} - VELA_DEPLOYMENT_SERVICE_PORT: {{ .Values.port | quote }} - VELA_SERVER_ROOT_URL: "https://{{ .Values.domain }}:{{ .Values.port }}" - VELA_ENABLE_DB_EXTERNAL_IPV6_LOADBALANCER: {{ .Values.controller.env.VELA_ENABLE_DB_EXTERNAL_IPV6_LOADBALANCER | quote }} - VELA_GRAFANA_URL: {{ .Values.controller.env.VELA_GRAFANA_URL | quote }} - VELA_SIMPLYBLOCK_CSI_NAMESPACE: {{ .Values.controller.env.VELA_SIMPLYBLOCK_CSI_NAMESPACE | quote }} ---- -apiVersion: v1 -kind: Service -metadata: - name: vela-controller-service -spec: - selector: - app.kubernetes.io/name: vela-controller - ports: - - name: http - port: 8000 - targetPort: 8000 - protocol: TCP - ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: vela-controller -spec: - parentRefs: - - name: {{ .Values.gateway.name }} - namespace: {{ .Values.gateway.namespace }} - hostnames: - - "{{ .Values.domain }}" - rules: - - matches: - - path: - type: PathPrefix - value: /vela - backendRefs: - - name: vela-controller-service - namespace: {{ .Release.Namespace }} - port: 8000 diff --git a/chart/templates/controller/configmap.yaml b/chart/templates/controller/configmap.yaml new file mode 100644 index 000000000..466252610 --- /dev/null +++ b/chart/templates/controller/configmap.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: vela-controller-config +data: + VELA_ROOT_PATH: {{ .Values.controller.env.VELA_ROOT_PATH | quote }} + VELA_JWT_SECRET: {{ .Values.controller.env.VELA_JWT_SECRET | quote }} + VELA_GATEWAY_NAME: {{ .Values.gateway.name | quote }} + VELA_GATEWAY_NAMESPACE: {{ .Values.gateway.namespace | quote }} + VELA_PGMETA_CRYPTO_KEY: {{ .Values.controller.env.VELA_PGMETA_CRYPTO_KEY | quote }} + VELA_CORS_ORIGINS: {{ .Values.controller.env.VELA_CORS_ORIGINS | quote }} + VELA_KEYCLOAK_URL: {{ .Values.controller.env.VELA_KEYCLOAK_URL | quote }} + VELA_KEYCLOAK_ADMIN_NAME: {{ .Values.controller.env.VELA_KEYCLOAK_ADMIN_NAME | quote }} + VELA_KEYCLOAK_ADMIN_SECRET: {{ .Values.controller.env.VELA_KEYCLOAK_ADMIN_SECRET | quote }} + VELA_CLOUDFLARE__API_TOKEN: {{ .Values.controller.env.VELA_CLOUDFLARE_API_TOKEN | quote }} + VELA_CLOUDFLARE__ZONE_ID: {{ .Values.controller.env.VELA_CLOUDFLARE_ZONE_ID | quote }} + VELA_CLOUDFLARE__BRANCH_REF: {{ .Values.controller.env.VELA_BRANCH_REF | quote }} + VELA_CLOUDFLARE__BRANCH_DB_REF: {{ .Values.controller.env.VELA_BRANCH_DB_REF | quote }} + VELA_CLOUDFLARE__DOMAIN_SUFFIX: {{ .Values.domainSuffix | default .Values.domain | quote }} + VELA_DEPLOYMENT_NAMESPACE_PREFIX: {{ .Values.controller.env.VELA_DEPLOYMENT_NAMESPACE_PREFIX | quote }} + VELA_DEPLOYMENT_SERVICE_PORT: {{ .Values.port | quote }} + VELA_SERVER_ROOT_URL: "https://{{ .Values.domain }}:{{ .Values.port }}" + VELA_ENABLE_DB_EXTERNAL_IPV6_LOADBALANCER: {{ .Values.controller.env.VELA_ENABLE_DB_EXTERNAL_IPV6_LOADBALANCER | quote }} + VELA_GRAFANA_URL: {{ .Values.controller.env.VELA_GRAFANA_URL | quote }} + VELA_SIMPLYBLOCK_CSI_NAMESPACE: {{ .Values.controller.env.VELA_SIMPLYBLOCK_CSI_NAMESPACE | quote }} diff --git a/chart/templates/controller/deployment.yaml b/chart/templates/controller/deployment.yaml new file mode 100644 index 000000000..161e86367 --- /dev/null +++ b/chart/templates/controller/deployment.yaml @@ -0,0 +1,93 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vela-controller + labels: + app.kubernetes.io/name: vela-controller + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: vela-controller + template: + metadata: + labels: + app.kubernetes.io/name: vela-controller + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + serviceAccountName: vela-controller + initContainers: +{{ include "vela.waitForPostgresInitContainer" (dict + "secretName" "vela-controller-secret" + "usernameKey" "controller-db-username" + "passwordKey" "controller-db-password" + "database" "controller" + ) | nindent 8 }} + containers: + - name: vela-controller + image: "{{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag }}" + imagePullPolicy: Always + ports: + - name: http + containerPort: 8000 + protocol: TCP + envFrom: + - configMapRef: + name: vela-controller-config + env: + - name: VELA_GRAFANA_SECURITY_ADMIN_USER + valueFrom: + secretKeyRef: + name: vela-grafana-secret + key: VELA_GRAFANA_SECURITY_ADMIN_USER + - name: VELA_GRAFANA_SECURITY_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: vela-grafana-secret + key: VELA_GRAFANA_SECURITY_ADMIN_PASSWORD + - name: CONTROLLER_DB_PASSWORD + valueFrom: + secretKeyRef: + name: vela-controller-secret + key: controller-db-password + - name: CELERY_DB_PASSWORD + valueFrom: + secretKeyRef: + name: vela-controller-secret + key: celery-db-password + - name: VELA_DEPLOYMENT_PASSWORD_SECRET + valueFrom: + secretKeyRef: + name: vela-controller-secret + key: deployment-password-secret + - name: DB_HOST + value: database + - name: VELA_POSTGRES_URL + value: 'postgresql+asyncpg://vela_controller:$(CONTROLLER_DB_PASSWORD)@$(DB_HOST):5432/controller' + - name: VELA_BROKER_URL + value: 'sqla+postgresql+psycopg://vela_celery:$(CELERY_DB_PASSWORD)@$(DB_HOST):5432/controller' + - name: VELA_RESULT_BACKEND + value: 'db+postgresql+psycopg://vela_celery:$(CELERY_DB_PASSWORD)@$(DB_HOST):5432/controller' + - name: VELA_GRAFANA_URL + value: "https://{{ .Values.domain }}:{{ .Values.port }}/grafana" + livenessProbe: + httpGet: + path: /health + port: http + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + {{- with .Values.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/chart/templates/controller/rbac.yaml b/chart/templates/controller/rbac.yaml new file mode 100644 index 000000000..9bc42f6fd --- /dev/null +++ b/chart/templates/controller/rbac.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: vela-controller + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: vela-controller-{{ .Release.Namespace }}-admin +subjects: + - kind: ServiceAccount + name: vela-controller + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin diff --git a/chart/templates/controller/secret.yaml b/chart/templates/controller/secret.yaml new file mode 100644 index 000000000..dfc43083e --- /dev/null +++ b/chart/templates/controller/secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: vela-controller-secret + namespace: {{ .Release.Namespace }} +type: Opaque +data: + {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace "vela-controller-secret" }} + deployment-password-secret: {{ if $existingSecret }}{{ index $existingSecret.data "deployment-password-secret" }}{{ else }}{{ randAlphaNum 32 | b64enc }}{{ end }} + controller-db-password: {{ include "vela.dbPassword" (list "controller-db-password" .) | b64enc }} + celery-db-password: {{ include "vela.dbPassword" (list "celery-db-password" .) | b64enc }} + controller-db-username: {{ "vela_controller" | b64enc }} diff --git a/chart/templates/controller/service.yaml b/chart/templates/controller/service.yaml new file mode 100644 index 000000000..18532eaca --- /dev/null +++ b/chart/templates/controller/service.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Service +metadata: + name: vela-controller-service +spec: + selector: + app.kubernetes.io/name: vela-controller + ports: + - name: http + port: 8000 + targetPort: 8000 + protocol: TCP + +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: vela-controller +spec: + parentRefs: + - name: {{ .Values.gateway.name }} + namespace: {{ .Values.gateway.namespace }} + hostnames: + - "{{ .Values.domain }}" + rules: + - matches: + - path: + type: PathPrefix + value: /vela + backendRefs: + - name: vela-controller-service + namespace: {{ .Release.Namespace }} + port: 8000 diff --git a/chart/templates/controller/worker.yaml b/chart/templates/controller/worker.yaml new file mode 100644 index 000000000..bd1240e19 --- /dev/null +++ b/chart/templates/controller/worker.yaml @@ -0,0 +1,76 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vela-controller-worker + labels: + app.kubernetes.io/name: vela-controller-worker + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: vela-controller-worker + template: + metadata: + labels: + app.kubernetes.io/name: vela-controller-worker + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + serviceAccountName: vela-controller + initContainers: +{{ include "vela.waitForPostgresInitContainer" (dict + "secretName" "vela-controller-secret" + "usernameKey" "controller-db-username" + "passwordKey" "controller-db-password" + "database" "controller" + ) | nindent 8 }} + containers: + - name: vela-controller-worker + image: "{{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag }}" + imagePullPolicy: Always + command: ["celery", "-A", "simplyblock.vela.worker", "worker", "--loglevel=info"] + envFrom: + - configMapRef: + name: vela-controller-config + env: + - name: VELA_GRAFANA_SECURITY_ADMIN_USER + valueFrom: + secretKeyRef: + name: vela-grafana-secret + key: VELA_GRAFANA_SECURITY_ADMIN_USER + - name: VELA_GRAFANA_SECURITY_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: vela-grafana-secret + key: VELA_GRAFANA_SECURITY_ADMIN_PASSWORD + - name: CONTROLLER_DB_PASSWORD + valueFrom: + secretKeyRef: + name: vela-controller-secret + key: controller-db-password + - name: CELERY_DB_PASSWORD + valueFrom: + secretKeyRef: + name: vela-controller-secret + key: celery-db-password + - name: VELA_DEPLOYMENT_PASSWORD_SECRET + valueFrom: + secretKeyRef: + name: vela-controller-secret + key: deployment-password-secret + - name: DB_HOST + value: database + - name: VELA_POSTGRES_URL + value: 'postgresql+asyncpg://vela_controller:$(CONTROLLER_DB_PASSWORD)@$(DB_HOST):5432/controller' + - name: VELA_BROKER_URL + value: 'sqla+postgresql+psycopg://vela_celery:$(CELERY_DB_PASSWORD)@$(DB_HOST):5432/controller' + - name: VELA_RESULT_BACKEND + value: 'db+postgresql+psycopg://vela_celery:$(CELERY_DB_PASSWORD)@$(DB_HOST):5432/controller' + {{- with .Values.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/chart/templates/database.yaml b/chart/templates/database.yaml index ebca909b5..6587e3b98 100644 --- a/chart/templates/database.yaml +++ b/chart/templates/database.yaml @@ -46,3 +46,6 @@ spec: replication: mode: async role: ha-read + managedSql: + scripts: + - sgScript: controller-db-init diff --git a/chart/templates/db-init-script-secret.yaml b/chart/templates/db-init-script-secret.yaml new file mode 100644 index 000000000..a7d0a9e75 --- /dev/null +++ b/chart/templates/db-init-script-secret.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: Secret +metadata: + name: vela-db-init-sql + namespace: {{ .Release.Namespace }} +stringData: + create-database.sql: | + CREATE DATABASE controller; + + init.sql: | + DO $$ + BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'vela_controller') THEN + CREATE USER vela_controller WITH PASSWORD '{{ include "vela.dbPassword" (list "controller-db-password" .) }}'; + ELSE + ALTER USER vela_controller WITH PASSWORD '{{ include "vela.dbPassword" (list "controller-db-password" .) }}'; + END IF; + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'vela_celery') THEN + CREATE USER vela_celery WITH PASSWORD '{{ include "vela.dbPassword" (list "celery-db-password" .) }}'; + ELSE + ALTER USER vela_celery WITH PASSWORD '{{ include "vela.dbPassword" (list "celery-db-password" .) }}'; + END IF; + END $$; + + GRANT ALL PRIVILEGES ON DATABASE controller TO vela_controller; + ALTER DATABASE controller OWNER TO vela_controller; + GRANT CONNECT ON DATABASE controller TO vela_celery; + + init-controller-db.sql: | + CREATE SCHEMA IF NOT EXISTS celery AUTHORIZATION vela_celery; + ALTER ROLE vela_celery IN DATABASE controller SET search_path TO celery; + + DO $$ + DECLARE r RECORD; + BEGIN + FOR r IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' LOOP + EXECUTE format('ALTER TABLE public.%I OWNER TO vela_controller', r.tablename); + END LOOP; + FOR r IN SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'public' LOOP + EXECUTE format('ALTER SEQUENCE public.%I OWNER TO vela_controller', r.sequence_name); + END LOOP; + END $$; diff --git a/chart/templates/db-init-sgscript.yaml b/chart/templates/db-init-sgscript.yaml new file mode 100644 index 000000000..b4354f225 --- /dev/null +++ b/chart/templates/db-init-sgscript.yaml @@ -0,0 +1,27 @@ +apiVersion: stackgres.io/v1 +kind: SGScript +metadata: + name: controller-db-init + namespace: {{ .Release.Namespace }} +spec: + continueOnError: true + scripts: + - id: 1 + name: create-database + scriptFrom: + secretKeyRef: + name: vela-db-init-sql + key: create-database.sql + - id: 2 + name: init-users-and-grants + scriptFrom: + secretKeyRef: + name: vela-db-init-sql + key: init.sql + - id: 3 + name: init-controller-db-schemas + database: controller + scriptFrom: + secretKeyRef: + name: vela-db-init-sql + key: init-controller-db.sql diff --git a/chart/templates/db-migration-job.yaml b/chart/templates/db-migration-job.yaml new file mode 100644 index 000000000..1343f8fef --- /dev/null +++ b/chart/templates/db-migration-job.yaml @@ -0,0 +1,64 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: vela-db-migration + namespace: {{ .Release.Namespace }} + annotations: + helm.sh/hook: pre-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + template: + spec: + restartPolicy: Never + containers: + - name: db-migration + image: postgres:17-alpine + env: + - name: DB_HOST + value: database + - name: SUPERUSER + valueFrom: + secretKeyRef: + name: database + key: superuser-username + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: database + key: superuser-password + command: ["/bin/sh", "-c"] + args: + - | + until pg_isready -h "$DB_HOST" -U "$SUPERUSER"; do + echo "Waiting for database..." + sleep 2 + done + + # Create controller database if it doesn't exist yet. + # The SGScript normally handles this, but it runs asynchronously after + # the upgrade so the migration job must bootstrap the database itself. + psql -h "$DB_HOST" -U "$SUPERUSER" -d postgres \ + -c "CREATE DATABASE controller" 2>/dev/null || true + + if psql -h "$DB_HOST" -U "$SUPERUSER" -d controller \ + -c "SELECT 1 FROM alembic_version LIMIT 1" 2>/dev/null; then + echo "Migration already done, skipping." + exit 0 + fi + + if ! psql -h "$DB_HOST" -U "$SUPERUSER" -d postgres \ + -c "SELECT 1 FROM alembic_version LIMIT 1" 2>/dev/null; then + echo "No source data found, skipping migration." + exit 0 + fi + + echo "Migrating data from postgres to controller database..." + pg_dump -h "$DB_HOST" -U "$SUPERUSER" -d postgres \ + --schema=public --no-owner --no-privileges \ + --exclude-table=kombu_message \ + --exclude-table=kombu_queue \ + --exclude-table=celery_taskmeta \ + --exclude-table=celery_tasksetmeta \ + | psql -h "$DB_HOST" -U "$SUPERUSER" -d controller + echo "Migration complete." diff --git a/chart/templates/vector-config.yaml b/chart/templates/vector-config.yaml index 9d0a294c8..7b288427a 100644 --- a/chart/templates/vector-config.yaml +++ b/chart/templates/vector-config.yaml @@ -126,16 +126,23 @@ data: inputs: - router.controller source: |- - parsed, err = parse_regex(.event_message, r'^(?P