246 lines
7.2 KiB
Go
246 lines
7.2 KiB
Go
package cluster
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/lib/pq"
|
|
|
|
"github.com/zalando/postgres-operator/pkg/spec"
|
|
"github.com/zalando/postgres-operator/pkg/util/constants"
|
|
"github.com/zalando/postgres-operator/pkg/util/retryutil"
|
|
)
|
|
|
|
const (
|
|
getUserSQL = `SELECT a.rolname, COALESCE(a.rolpassword, ''), a.rolsuper, a.rolinherit,
|
|
a.rolcreaterole, a.rolcreatedb, a.rolcanlogin, s.setconfig,
|
|
ARRAY(SELECT b.rolname
|
|
FROM pg_catalog.pg_auth_members m
|
|
JOIN pg_catalog.pg_authid b ON (m.roleid = b.oid)
|
|
WHERE m.member = a.oid) as memberof
|
|
FROM pg_catalog.pg_authid a LEFT JOIN pg_db_role_setting s ON (a.oid = s.setrole AND s.setdatabase = 0::oid)
|
|
WHERE a.rolname = ANY($1)
|
|
ORDER BY 1;`
|
|
|
|
getDatabasesSQL = `SELECT datname, pg_get_userbyid(datdba) AS owner FROM pg_database;`
|
|
createDatabaseSQL = `CREATE DATABASE "%s" OWNER "%s";`
|
|
alterDatabaseOwnerSQL = `ALTER DATABASE "%s" OWNER TO "%s";`
|
|
)
|
|
|
|
func (c *Cluster) pgConnectionString() string {
|
|
password := c.systemUsers[constants.SuperuserKeyName].Password
|
|
|
|
return fmt.Sprintf("host='%s' dbname=postgres sslmode=require user='%s' password='%s' connect_timeout='%d'",
|
|
fmt.Sprintf("%s.%s.svc.cluster.local", c.Name, c.Namespace),
|
|
c.systemUsers[constants.SuperuserKeyName].Name,
|
|
strings.Replace(password, "$", "\\$", -1),
|
|
constants.PostgresConnectTimeout/time.Second)
|
|
}
|
|
|
|
func (c *Cluster) databaseAccessDisabled() bool {
|
|
if !c.OpConfig.EnableDBAccess {
|
|
c.logger.Debugf("database access is disabled")
|
|
}
|
|
|
|
return !c.OpConfig.EnableDBAccess
|
|
}
|
|
|
|
func (c *Cluster) initDbConn() error {
|
|
c.setProcessName("initializing db connection")
|
|
if c.pgDb != nil {
|
|
return nil
|
|
}
|
|
|
|
var conn *sql.DB
|
|
connstring := c.pgConnectionString()
|
|
|
|
finalerr := retryutil.Retry(constants.PostgresConnectTimeout, constants.PostgresConnectRetryTimeout,
|
|
func() (bool, error) {
|
|
var err error
|
|
conn, err = sql.Open("postgres", connstring)
|
|
if err == nil {
|
|
err = conn.Ping()
|
|
}
|
|
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
if _, ok := err.(*net.OpError); ok {
|
|
c.logger.Errorf("could not connect to PostgreSQL database: %v", err)
|
|
return false, nil
|
|
}
|
|
|
|
if err2 := conn.Close(); err2 != nil {
|
|
c.logger.Errorf("error when closing PostgreSQL connection after another error: %v", err)
|
|
return false, err2
|
|
}
|
|
|
|
return false, err
|
|
})
|
|
|
|
if finalerr != nil {
|
|
return fmt.Errorf("could not init db connection: %v", finalerr)
|
|
}
|
|
// Limit ourselves to a single connection and allow no idle connections.
|
|
conn.SetMaxOpenConns(1)
|
|
conn.SetMaxIdleConns(-1)
|
|
|
|
c.pgDb = conn
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cluster) closeDbConn() (err error) {
|
|
c.setProcessName("closing db connection")
|
|
if c.pgDb != nil {
|
|
c.logger.Debug("closing database connection")
|
|
if err = c.pgDb.Close(); err != nil {
|
|
c.logger.Errorf("could not close database connection: %v", err)
|
|
}
|
|
c.pgDb = nil
|
|
|
|
return nil
|
|
}
|
|
c.logger.Warning("attempted to close an empty db connection object")
|
|
return nil
|
|
}
|
|
|
|
func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUserMap, err error) {
|
|
c.setProcessName("reading users from the db")
|
|
var rows *sql.Rows
|
|
users = make(spec.PgUserMap)
|
|
if rows, err = c.pgDb.Query(getUserSQL, pq.Array(userNames)); err != nil {
|
|
return nil, fmt.Errorf("error when querying users: %v", err)
|
|
}
|
|
defer func() {
|
|
if err2 := rows.Close(); err2 != nil {
|
|
err = fmt.Errorf("error when closing query cursor: %v", err2)
|
|
}
|
|
}()
|
|
|
|
for rows.Next() {
|
|
var (
|
|
rolname, rolpassword string
|
|
rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool
|
|
roloptions, memberof []string
|
|
)
|
|
err := rows.Scan(&rolname, &rolpassword, &rolsuper, &rolinherit,
|
|
&rolcreaterole, &rolcreatedb, &rolcanlogin, pq.Array(&roloptions), pq.Array(&memberof))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error when processing user rows: %v", err)
|
|
}
|
|
flags := makeUserFlags(rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin)
|
|
// XXX: the code assumes the password we get from pg_authid is always MD5
|
|
parameters := make(map[string]string)
|
|
for _, option := range roloptions {
|
|
fields := strings.Split(option, "=")
|
|
if len(fields) != 2 {
|
|
c.logger.Warningf("skipping malformed option: %q", option)
|
|
continue
|
|
}
|
|
parameters[fields[0]] = fields[1]
|
|
}
|
|
|
|
users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof, Parameters: parameters}
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
// getDatabases returns the map of current databases with owners
|
|
// The caller is responsible for opening and closing the database connection
|
|
func (c *Cluster) getDatabases() (dbs map[string]string, err error) {
|
|
var (
|
|
rows *sql.Rows
|
|
)
|
|
|
|
if rows, err = c.pgDb.Query(getDatabasesSQL); err != nil {
|
|
return nil, fmt.Errorf("could not query database: %v", err)
|
|
}
|
|
|
|
defer func() {
|
|
if err2 := rows.Close(); err2 != nil {
|
|
if err != nil {
|
|
err = fmt.Errorf("error when closing query cursor: %v, previous error: %v", err2, err)
|
|
} else {
|
|
err = fmt.Errorf("error when closing query cursor: %v", err2)
|
|
}
|
|
}
|
|
}()
|
|
|
|
dbs = make(map[string]string)
|
|
|
|
for rows.Next() {
|
|
var datname, owner string
|
|
|
|
if err = rows.Scan(&datname, &owner); err != nil {
|
|
return nil, fmt.Errorf("error when processing row: %v", err)
|
|
}
|
|
dbs[datname] = owner
|
|
}
|
|
|
|
return dbs, err
|
|
}
|
|
|
|
// executeCreateDatabase creates new database with the given owner.
|
|
// The caller is responsible for openinging and closing the database connection.
|
|
func (c *Cluster) executeCreateDatabase(datname, owner string) error {
|
|
return c.execCreateOrAlterDatabase(datname, owner, createDatabaseSQL,
|
|
"creating database", "create database")
|
|
}
|
|
|
|
// executeCreateDatabase changes the owner of the given database.
|
|
// The caller is responsible for openinging and closing the database connection.
|
|
func (c *Cluster) executeAlterDatabaseOwner(datname string, owner string) error {
|
|
return c.execCreateOrAlterDatabase(datname, owner, alterDatabaseOwnerSQL,
|
|
"changing owner for database", "alter database owner")
|
|
}
|
|
|
|
func (c *Cluster) execCreateOrAlterDatabase(datname, owner, statement, doing, operation string) error {
|
|
if !c.databaseNameOwnerValid(datname, owner) {
|
|
return nil
|
|
}
|
|
c.logger.Infof("%s %q owner %q", doing, datname, owner)
|
|
if _, err := c.pgDb.Exec(fmt.Sprintf(statement, datname, owner)); err != nil {
|
|
return fmt.Errorf("could not execute %s: %v", operation, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Cluster) databaseNameOwnerValid(datname, owner string) bool {
|
|
if _, ok := c.pgUsers[owner]; !ok {
|
|
c.logger.Infof("skipping creation of the %q database, user %q does not exist", datname, owner)
|
|
return false
|
|
}
|
|
|
|
if !databaseNameRegexp.MatchString(datname) {
|
|
c.logger.Infof("database %q has invalid name", datname)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func makeUserFlags(rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool) (result []string) {
|
|
if rolsuper {
|
|
result = append(result, constants.RoleFlagSuperuser)
|
|
}
|
|
if rolinherit {
|
|
result = append(result, constants.RoleFlagInherit)
|
|
}
|
|
if rolcreaterole {
|
|
result = append(result, constants.RoleFlagCreateRole)
|
|
}
|
|
if rolcreatedb {
|
|
result = append(result, constants.RoleFlagCreateDB)
|
|
}
|
|
if rolcanlogin {
|
|
result = append(result, constants.RoleFlagLogin)
|
|
}
|
|
|
|
return result
|
|
}
|