db provisioning
This commit is contained in:
parent
af8bbf536f
commit
1293ef678e
8 changed files with 151 additions and 38 deletions
83
internal/app/db_provisioning.go
Normal file
83
internal/app/db_provisioning.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
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)
|
||||
}
|
||||
|
||||
if _, err := adminDB.ExecContext(ctx, fmt.Sprintf(`DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = %s) THEN
|
||||
CREATE DATABASE %s OWNER %s;
|
||||
END IF;
|
||||
END $$;`, sqlQuoteLiteral(p.DBName), 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, `'`, `''`) + `'`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue