From f46e453ed0367fe821641868b55afcefd5d82e86 Mon Sep 17 00:00:00 2001 From: git_admin Date: Mon, 27 Apr 2026 11:55:36 +0000 Subject: [PATCH] chart: db-init initContainer (auto-create tenant DB + base init) + pin -d --- templates/odoo-deployment.yaml | 108 +++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/templates/odoo-deployment.yaml b/templates/odoo-deployment.yaml index 00e5b45..e131c51 100644 --- a/templates/odoo-deployment.yaml +++ b/templates/odoo-deployment.yaml @@ -28,22 +28,80 @@ spec: # values.yaml would leave the existing pod alone. odoosky.io/addons-hash: {{ .Values.addons | toJson | sha256sum | trunc 16 }} spec: - {{- if .Values.addons }} - # One initContainer per selected addon. Each addon image's - # ENTRYPOINT/CMD copies its bundled content into /target/, - # which is the shared volume the Odoo container reads from. - # Init containers run in order, but each writes to its own - # subdir, so order doesn't matter. - # - # `imagePullPolicy: Always` is intentional: addon images can be - # rebuilt under the same {code}:{version} tag (e.g. when Tower's - # build pipeline changes — adding pip-install layer for python - # external_dependencies). With IfNotPresent, kubelet would - # reuse the cached old digest and the pod would never see the - # new content. Always forces a manifest fetch on each pod - # start; layers themselves are still cached so the pull is - # cheap (~50 ms when content is unchanged, fresh otherwise). + # Bootstrap initContainers always run; addon initContainers only + # run when there are addons. We always need db-init to ensure the + # tenant DB exists + base module is initialized before Odoo's + # main process starts serving — without this the operator would + # have to click through Odoo's setup wizard manually for every + # new instance, which is exactly the SaaS UX v3 sets out to + # eliminate. initContainers: + # db-init: idempotent on every pod boot. + # 1. createdb if missing (PG-level) + # 2. odoo -i base --stop-after-init to install base module + # and create Odoo's tables. After base is installed, + # `-i base` is a no-op so subsequent boots add ~5s. + - name: db-init + image: "{{ .Values.odoo.image }}:{{ .Values.odoo.tag }}" + imagePullPolicy: IfNotPresent + # Override the official Odoo entrypoint so we can run psql + # before odoo. The image ships with postgresql-client, so + # createdb is on PATH. + command: ["/bin/sh", "-c"] + args: + - | + set -eu + DBNAME="{{ .Values.instance.code }}" + echo "── ensuring database $DBNAME exists ──" + # Wait for PG to accept connections (max 60s) + for i in $(seq 1 30); do + if PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d postgres -c '\q' 2>/dev/null; then break; fi + echo "(waiting for postgres, $i/30)" + sleep 2 + done + # createdb is idempotent if we wrap with an existence check. + if PGPASSWORD="$PASSWORD" psql -h "$HOST" -p "$PORT" -U "$USER" -d postgres -tAc \ + "SELECT 1 FROM pg_database WHERE datname = '$DBNAME'" | grep -q 1; then + echo "(database $DBNAME already exists)" + else + echo "── creating database $DBNAME ──" + PGPASSWORD="$PASSWORD" createdb -h "$HOST" -p "$PORT" -U "$USER" "$DBNAME" + fi + echo "── initializing base module (no-op if already installed) ──" + odoo -i base -d "$DBNAME" --stop-after-init --without-demo=all --no-http \ + --db_host="$HOST" --db_port="$PORT" --db_user="$USER" --db_password="$PASSWORD" \ + --workers=0 + echo "── db-init done ──" + env: + - name: HOST + value: {{ include "instance.fullname" . }}-pg + - name: PORT + value: "5432" + - name: USER + valueFrom: + secretKeyRef: + name: {{ include "instance.fullname" . }}-pg + key: POSTGRES_USER + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "instance.fullname" . }}-pg + key: POSTGRES_PASSWORD + {{- if .Values.addons }} + # One initContainer per selected addon. Each addon image's + # ENTRYPOINT/CMD copies its bundled content into /target/, + # which is the shared volume the Odoo container reads from. + # Init containers run in order, but each writes to its own + # subdir, so order doesn't matter. + # + # `imagePullPolicy: Always` is intentional: addon images can be + # rebuilt under the same {code}:{version} tag (e.g. when Tower's + # build pipeline changes — adding pip-install layer for python + # external_dependencies). With IfNotPresent, kubelet would + # reuse the cached old digest and the pod would never see the + # new content. Always forces a manifest fetch on each pod + # start; layers themselves are still cached so the pull is + # cheap (~50 ms when content is unchanged, fresh otherwise). {{- range $i, $a := .Values.addons }} - name: addon-{{ $a.code | replace "_" "-" | lower }} image: {{ $a.image }}:{{ $a.version }} @@ -52,21 +110,23 @@ spec: - name: addons mountPath: /target {{- end }} - {{- end }} + {{- end }} containers: - name: odoo image: "{{ .Values.odoo.image }}:{{ .Values.odoo.tag }}" imagePullPolicy: IfNotPresent - {{- if .Values.addons }} - # Pass --addons-path explicitly so the official Odoo image's - # entrypoint appends our extra-addons dir to the default - # addons_path. Args after the entrypoint that start with -- - # are passed through to `odoo` directly. We list our path - # AFTER the default so built-in modules still take priority - # on name collisions. + # Pin the active database to our tenant code. Without this + # Odoo runs in multi-DB mode and exposes /web/database/manager; + # for the SaaS UX we want one instance == one DB and never + # show the manager. db-init has already created and bootstrapped + # this DB, so Odoo opens it cleanly. args: + - "-d" + - "{{ .Values.instance.code }}" + - "--db-filter=^{{ .Values.instance.code }}$" + {{- if .Values.addons }} - "--addons-path=/usr/lib/python3/dist-packages/odoo/addons,{{ .Values.addonsMountPath }}" - {{- end }} + {{- end }} ports: - name: http containerPort: 8069