84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
func (a *App) ensureProjectDatabase(ctx context.Context, p *Project) error {
|
|
if !a.cfg.DBProvisionEnabled {
|
|
return nil
|
|
}
|
|
adminDB, err := sql.Open("pgx", a.cfg.DBProvisionAdminURL)
|
|
if err != nil {
|
|
return fmt.Errorf("db provisioning connect failed: %w", err)
|
|
}
|
|
defer adminDB.Close()
|
|
if err := adminDB.PingContext(ctx); err != nil {
|
|
return fmt.Errorf("db provisioning ping failed: %w", err)
|
|
}
|
|
|
|
if p.DBName == "" {
|
|
p.DBName = fmt.Sprintf("app_%d", p.ID)
|
|
}
|
|
if p.DBUser == "" {
|
|
p.DBUser = fmt.Sprintf("appu_%d", p.ID)
|
|
}
|
|
if p.DBPassword == "" {
|
|
pw, err := newDeployToken()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.DBPassword = pw
|
|
}
|
|
|
|
if _, err := adminDB.ExecContext(ctx, fmt.Sprintf(`DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %s) THEN
|
|
CREATE ROLE %s LOGIN PASSWORD %s;
|
|
ELSE
|
|
ALTER ROLE %s LOGIN PASSWORD %s;
|
|
END IF;
|
|
END $$;`, sqlQuoteLiteral(p.DBUser), sqlQuoteIdent(p.DBUser), sqlQuoteLiteral(p.DBPassword), sqlQuoteIdent(p.DBUser), sqlQuoteLiteral(p.DBPassword))); err != nil {
|
|
return fmt.Errorf("create/alter role failed: %w", err)
|
|
}
|
|
|
|
var dbExists bool
|
|
if err := adminDB.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname = $1)`, p.DBName).Scan(&dbExists); err != nil {
|
|
return fmt.Errorf("check database existence failed: %w", err)
|
|
}
|
|
if !dbExists {
|
|
if _, err := adminDB.ExecContext(ctx, fmt.Sprintf(`CREATE DATABASE %s OWNER %s TEMPLATE template0`, sqlQuoteIdent(p.DBName), sqlQuoteIdent(p.DBUser))); err != nil {
|
|
return fmt.Errorf("create database failed: %w", err)
|
|
}
|
|
}
|
|
|
|
if _, err := a.db.ExecContext(ctx, `UPDATE projects SET db_name=$1, db_user=$2, db_password=$3 WHERE id=$4`, p.DBName, p.DBUser, p.DBPassword, p.ID); err != nil {
|
|
return fmt.Errorf("persist db credentials failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) projectDatabaseURL(p Project) (string, error) {
|
|
if p.DBName == "" || p.DBUser == "" || p.DBPassword == "" {
|
|
return "", nil
|
|
}
|
|
base, err := url.Parse(a.cfg.DBProvisionAdminURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
base.User = url.UserPassword(p.DBUser, p.DBPassword)
|
|
base.Path = "/" + p.DBName
|
|
return base.String(), nil
|
|
}
|
|
|
|
func sqlQuoteIdent(s string) string {
|
|
return `"` + strings.ReplaceAll(s, `"`, `""`) + `"`
|
|
}
|
|
|
|
func sqlQuoteLiteral(s string) string {
|
|
return `'` + strings.ReplaceAll(s, `'`, `''`) + `'`
|
|
}
|