more updates
Publish Action Image / build (push) Successful in 1m8s

This commit is contained in:
2026-06-29 15:14:55 +10:00
parent 01223c176f
commit 4ee1b9e13c
15 changed files with 1155 additions and 832 deletions
+80 -28
View File
@@ -2,7 +2,6 @@ package generator
import (
"fmt"
"strings"
"kforge/internal/config"
"kforge/pkg/interpolate"
@@ -79,18 +78,22 @@ type InfraManifest struct {
}
// ------------------------------------------------------------
// Database — CNPG
// Database — CNPG (centralized cluster)
// ------------------------------------------------------------
func generateDatabase(env *config.ResolvedEnvironment, db *config.DatabaseInfraConfig) ([]InfraManifest, error) {
dbName := db.DatabaseName
if dbName == "" {
dbName = interpolate.Slug(env.FullName)
dbName = interpolate.PGIdentifier(env.FullName)
}
roleName := dbName + "_role"
// pgUser must be a valid unquoted PostgreSQL identifier; same as
// the username stored in the db-credentials Secret by `kforge secrets apply`.
pgUser := interpolate.PGIdentifier(env.FullName)
secretName := env.FullName + "-db-credentials"
cnpgHost := env.CNPGHost
// CNPG Database CR
// CNPG Database CR — declaratively manages the database lifecycle.
// The owner role is created by the db-init Job below.
dbManifest := fmt.Sprintf(`apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
@@ -103,47 +106,96 @@ spec:
name: %s
owner: %s
cluster:
name: cnpg-main
`, dbName, env.Namespace, env.FullName, dbName, roleName)
name: %s
`, dbName, env.Namespace, env.FullName, dbName, pgUser, env.CNPGClusterName)
// CNPG Role CR — CNPG creates and rotates the password,
// storing it in the secret named below.
roleManifest := fmt.Sprintf(`apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
// db-init Job — runs on every deploy to ensure the PostgreSQL role
// exists and its password matches the Secret. The password stored by
// `kforge secrets apply` is alphanumeric-only so it is safe to
// embed in a shell command without additional escaping.
//
// Prerequisites:
// - The CNPG superuser Secret must exist in the same namespace
// (or copy it there as part of cluster bootstrap).
// - `kforge secrets apply` must have run before this Job.
jobManifest := fmt.Sprintf(`apiVersion: batch/v1
kind: Job
metadata:
name: %s
name: %s-db-init
namespace: %s
labels:
app: %s
managed-by: kforge
spec:
name: %s
passwordSecret:
name: %s
login: true
superuser: false
createdb: false
`, roleName, env.Namespace, env.FullName, roleName, secretName)
// The env vars reference the CNPG-managed secret.
// CNPG populates: username, password keys in the secret.
// We assemble DATABASE_URL from the known CNPG host + db name.
cnpgHost := env.CNPGHost
dbURL := fmt.Sprintf("postgresql://$(%s_USER):$(%s_PASSWORD)@%s/%s",
strings.ToUpper(env.FullName), strings.ToUpper(env.FullName), cnpgHost, dbName)
ttlSecondsAfterFinished: 600
template:
metadata:
labels:
app: %s
spec:
restartPolicy: OnFailure
containers:
- name: db-init
image: postgres:16-alpine
command:
- /bin/sh
- -c
- |
set -e
echo "Ensuring role $DB_USER exists..."
PGPASSWORD="$ADMIN_PASSWORD" psql -h "$DB_HOST" -U "$ADMIN_USER" postgres \
-c "SELECT 1 FROM pg_roles WHERE rolname = '$DB_USER'" | grep -q 1 || \
PGPASSWORD="$ADMIN_PASSWORD" psql -h "$DB_HOST" -U "$ADMIN_USER" postgres \
-c "CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASSWORD';"
echo "Syncing password for $DB_USER..."
PGPASSWORD="$ADMIN_PASSWORD" psql -h "$DB_HOST" -U "$ADMIN_USER" postgres \
-c "ALTER ROLE $DB_USER WITH PASSWORD '$DB_PASSWORD';"
echo "Done."
env:
- name: DB_HOST
value: %s
- name: DB_USER
valueFrom:
secretKeyRef:
name: %s
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: %s
key: password
- name: ADMIN_USER
valueFrom:
secretKeyRef:
name: %s
key: username
- name: ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: %s
key: password
`,
env.FullName, env.Namespace, env.FullName, env.FullName,
cnpgHost,
secretName, secretName,
env.CNPGSuperuserSecret, env.CNPGSuperuserSecret,
)
// DATABASE_URL uses Kubernetes $(VAR_NAME) substitution — DB_USER and
// DB_PASSWORD must be defined earlier in the env list.
envVars := []config.EnvVarConfig{
{Name: "DB_HOST", Type: config.EnvVarTypePlain, Value: cnpgHost},
{Name: "DB_PORT", Type: config.EnvVarTypePlain, Value: "5432"},
{Name: "DB_NAME", Type: config.EnvVarTypePlain, Value: dbName},
{Name: "DB_USER", Type: config.EnvVarTypeSecretRef, SecretName: secretName, SecretKey: "username"},
{Name: "DB_PASSWORD", Type: config.EnvVarTypeSecretRef, SecretName: secretName, SecretKey: "password"},
{Name: "DATABASE_URL", Type: config.EnvVarTypePlain, Value: dbURL},
{Name: "DATABASE_URL", Type: config.EnvVarTypePlain,
Value: fmt.Sprintf("postgresql://$(DB_USER):$(DB_PASSWORD)@%s/%s", cnpgHost, dbName)},
}
return []InfraManifest{
{Name: "cnpg-database", Content: dbManifest, EnvVars: envVars},
{Name: "cnpg-role", Content: roleManifest},
{Name: "cnpg-db-init", Content: jobManifest},
}, nil
}