Skip to content

Commit bcd0f20

Browse files
flamingdumpsterJeff McCormick
authored andcommitted
Update create pgbouncer to decouple dependency with postgres container. (#647)
* Checkpoint commit to merge in latest - not fully functional * Fix issues when adding pgbouncer to existing cluster * remove pgbouncer user option, update docs * disable pgbouncer for non-pgbouncer cluster
1 parent fbbc4ba commit bcd0f20

File tree

2 files changed

+158
-23
lines changed

2 files changed

+158
-23
lines changed

apiserver/clusterservice/clusterimpl.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -606,26 +606,26 @@ func CreateCluster(request *msgs.CreateClusterRequest, ns string) msgs.CreateClu
606606

607607
if request.PgbouncerFlag {
608608
userLabelsMap[util.LABEL_PGBOUNCER] = "true"
609-
}
610609

611-
// pgbouncer credentials are always added to primary in case pgbouncer is added later through
612-
// > pgo create pgbouncer mycluster
613-
// workaround to a current limitation by the postgres container in how it handles pgbouncer
610+
// need to create password to be added to postgres container and pgbouncer credential...
611+
if !(len(request.PgbouncerPass) > 0) {
612+
userLabelsMap[util.LABEL_PGBOUNCER_PASS] = util.GeneratePassword(10)
613+
} else {
614+
userLabelsMap[util.LABEL_PGBOUNCER_PASS] = request.PgbouncerPass
615+
}
616+
617+
// default pgbouncer user to "pgbouncer" - request should be empty until configurable user is implemented.
618+
if !(len(request.PgbouncerUser) > 0) {
619+
userLabelsMap[util.LABEL_PGBOUNCER_USER] = "pgbouncer"
620+
} else {
621+
622+
userLabelsMap[util.LABEL_PGBOUNCER_USER] = request.PgbouncerUser
623+
}
624+
625+
userLabelsMap[util.LABEL_PGBOUNCER_SECRET] = request.PgbouncerSecret
614626

615-
// need to create password to be added to postgres container and pgbouncer credential...
616-
/**
617-
if !(len(request.PgbouncerPass) > 0) {
618-
userLabelsMap[util.LABEL_PGBOUNCER_PASS] = util.GeneratePassword(10)
619-
} else {
620-
userLabelsMap[util.LABEL_PGBOUNCER_PASS] = request.PgbouncerPass
621-
}
622-
*/
623-
if request.PgbouncerPass != "" {
624-
userLabelsMap[util.LABEL_PGBOUNCER_PASS] = request.PgbouncerPass
625627
}
626628

627-
userLabelsMap[util.LABEL_PGBOUNCER_USER] = request.PgbouncerUser
628-
userLabelsMap[util.LABEL_PGBOUNCER_SECRET] = request.PgbouncerSecret
629629
log.Debug("userLabelsMap")
630630
log.Debugf("%v", userLabelsMap)
631631

operator/cluster/pgbouncer.go

Lines changed: 142 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,9 @@ func AddPgbouncer(clientset *kubernetes.Clientset, cl *crv1.Pgcluster, namespace
227227
replicaName := cl.Spec.Name + "-replica"
228228

229229
pgbouncerUser := cl.Spec.UserLabels[util.LABEL_PGBOUNCER_USER]
230-
// log.Debugf("userSpecifiedPass: %s", pgbouncerUser)
230+
// log.Debugf("userSpecifiedPass: %s", pgbouncerUser)
231231
pgbouncerPass := cl.Spec.UserLabels[util.LABEL_PGBOUNCER_PASS]
232-
// log.Debugf("userSpecifiedPass: %s", pgbouncerPass)
232+
// log.Debugf("userSpecifiedPass: %s", pgbouncerPass)
233233

234234
if updateCreds {
235235

@@ -255,7 +255,7 @@ func AddPgbouncer(clientset *kubernetes.Clientset, cl *crv1.Pgcluster, namespace
255255
clusterName := cl.Spec.Name
256256
pgbouncerName := clusterName + PGBOUNCER_SUFFIX
257257
log.Debugf("adding a pgbouncer %s", pgbouncerName)
258-
// log.Debugf("secretUser: %s, secretPass: %s", secretUser, secretPass)
258+
// log.Debugf("secretUser: %s, secretPass: %s", secretUser, secretPass)
259259

260260
//create the pgbouncer deployment
261261
fields := PgbouncerTemplateFields{
@@ -343,8 +343,30 @@ func updatePgBouncerCredentials(clientset *kubernetes.Clientset, namespace, user
343343
log.Debug("Error deleting pgbouncer secret, probaby not found, ignoring")
344344
}
345345

346-
connectionInfo := getDBUserInfo(namespace, clusterName, clientset)
346+
err, databaseNames := getDatabaseListForCredentials(namespace, clusterName, clientset)
347347

348+
if err != nil {
349+
log.Debug(err)
350+
return err
351+
}
352+
353+
for _, dbName := range databaseNames {
354+
355+
connectionInfo := getDBUserInfo(namespace, clusterName, dbName, clientset)
356+
357+
log.Debugf("Creating pgbouncer authorization in %s database", dbName)
358+
359+
err = createPgBouncerAuthInDB(clusterName, connectionInfo, username, namespace)
360+
361+
if err != nil {
362+
log.Debugf("Unable to create pgbouncer user in %s database", dbName)
363+
log.Debug(err.Error())
364+
return err
365+
}
366+
}
367+
368+
// update the password for the pgbouncer user in postgres database
369+
connectionInfo := getDBUserInfo(namespace, clusterName, "postgres", clientset)
348370
err = updatePgBouncerDBPassword(clusterName, connectionInfo, username, password, namespace)
349371

350372
if err != nil {
@@ -360,7 +382,7 @@ func updatePgBouncerDBPassword(clusterName string, p connectionInfo, username, n
360382
var err error
361383
var conn *sql.DB
362384

363-
log.Debugf("Updating password for %s in %s ", username, p.Database)
385+
// log.Debugf("Updating password for %s in %s with %s ", username, p.Database, newPassword)
364386

365387
conn, err = sql.Open("postgres", "sslmode=disable user="+p.Username+" host="+p.Hostip+" port="+p.Port+" dbname="+p.Database+" password="+p.Password)
366388
if err != nil {
@@ -389,7 +411,73 @@ func updatePgBouncerDBPassword(clusterName string, p connectionInfo, username, n
389411

390412
}
391413

392-
func getDBUserInfo(namespace, clusterName string, clientset *kubernetes.Clientset) connectionInfo {
414+
func createPgBouncerAuthInDB(clusterName string, p connectionInfo, username string, namespace string) error {
415+
416+
var err error
417+
var conn *sql.DB
418+
419+
log.Debugf("Creating %s user for pgbouncer in %s ", username, p.Database)
420+
421+
conn, err = sql.Open("postgres", "sslmode=disable user="+p.Username+" host="+p.Hostip+" port="+p.Port+" dbname="+p.Database+" password="+p.Password)
422+
if err != nil {
423+
log.Debug(err.Error())
424+
return err
425+
}
426+
427+
var rows *sql.Rows
428+
429+
// create pgbouncer role and setup authorization.
430+
querystr := `
431+
DO $$
432+
BEGIN
433+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'pgbouncer') THEN
434+
CREATE ROLE pgbouncer;
435+
END IF;
436+
END
437+
$$;
438+
439+
ALTER ROLE pgbouncer LOGIN;
440+
441+
CREATE SCHEMA IF NOT EXISTS pgbouncer AUTHORIZATION pgbouncer;
442+
443+
CREATE OR REPLACE FUNCTION pgbouncer.get_auth(p_username TEXT)
444+
RETURNS TABLE(username TEXT, password TEXT) AS
445+
$$
446+
BEGIN
447+
RAISE WARNING 'PgBouncer auth request: %', p_username;
448+
449+
RETURN QUERY
450+
SELECT rolname::TEXT, rolpassword::TEXT
451+
FROM pg_authid
452+
WHERE NOT rolsuper
453+
AND rolname = p_username;
454+
END;
455+
$$ LANGUAGE plpgsql SECURITY DEFINER;
456+
457+
REVOKE ALL ON FUNCTION pgbouncer.get_auth(p_username TEXT) FROM PUBLIC;
458+
GRANT EXECUTE ON FUNCTION pgbouncer.get_auth(p_username TEXT) TO pgbouncer; `
459+
460+
rows, err = conn.Query(querystr)
461+
462+
if err != nil {
463+
log.Debug(err.Error())
464+
return err
465+
}
466+
467+
defer func() {
468+
if conn != nil {
469+
conn.Close()
470+
}
471+
if rows != nil {
472+
rows.Close()
473+
}
474+
}()
475+
476+
return err
477+
478+
}
479+
480+
func getDBUserInfo(namespace, clusterName string, targetDB string, clientset *kubernetes.Clientset) connectionInfo {
393481
info := connectionInfo{}
394482

395483
//get the service for the cluster
@@ -410,7 +498,7 @@ func getDBUserInfo(namespace, clusterName string, clientset *kubernetes.Clientse
410498
for _, s := range secrets.Items {
411499
username = string(s.Data[util.LABEL_USERNAME][:])
412500
password = string(s.Data[util.LABEL_PASSWORD][:])
413-
database = "postgres"
501+
database = targetDB
414502
hostip = service.Spec.ClusterIP
415503
if username == "postgres" {
416504
log.Debug("got postgres user secrets")
@@ -428,6 +516,53 @@ func getDBUserInfo(namespace, clusterName string, clientset *kubernetes.Clientse
428516
return info
429517
}
430518

519+
func getDatabaseListForCredentials(namespace, clusterName string, clientSet *kubernetes.Clientset) (error, []string) {
520+
521+
info := getDBUserInfo(namespace, clusterName, "postgres", clientSet)
522+
523+
log.Debug("Getting list of database names to update for pgbouncer")
524+
525+
var databases []string
526+
527+
var err error
528+
var conn *sql.DB
529+
530+
conn, err = sql.Open("postgres", "sslmode=disable user="+info.Username+" host="+info.Hostip+" port="+info.Port+" dbname="+info.Database+" password="+info.Password)
531+
if err != nil {
532+
log.Debug(err.Error())
533+
return err, databases
534+
}
535+
536+
// get a list of database names from postgres
537+
var rows *sql.Rows
538+
querystr := "SELECT datname FROM pg_database WHERE datname NOT IN ('template0', 'template1')"
539+
rows, err = conn.Query(querystr)
540+
if err != nil {
541+
log.Debug(err.Error())
542+
return err, databases
543+
}
544+
545+
defer func() {
546+
if conn != nil {
547+
conn.Close()
548+
}
549+
if rows != nil {
550+
rows.Close()
551+
}
552+
}()
553+
554+
for rows.Next() {
555+
var dbName string
556+
if err := rows.Scan(&dbName); err != nil {
557+
log.Debug(err)
558+
}
559+
databases = append(databases, dbName)
560+
}
561+
562+
return err, databases
563+
564+
}
565+
431566
// CreatePgbouncerSecret create a secret used by pgbouncer
432567
func createPgbouncerSecret(clientset *kubernetes.Clientset, cl *crv1.Pgcluster, primary, replica, db, secretName, namespace string) (error, string, string) {
433568

0 commit comments

Comments
 (0)