diff --git a/manifests/testpostgresql.yaml b/manifests/testpostgresql.yaml index e80d667ec..8f5412f3d 100644 --- a/manifests/testpostgresql.yaml +++ b/manifests/testpostgresql.yaml @@ -14,7 +14,8 @@ spec: - createdb allowedSourceRanges: #Load balancer source ranges - 127.0.0.1/32 - + databases: + foo: zalando #Expert section postgresql: version: "9.6" diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 8e77e8a88..3ca6d79f0 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -228,10 +228,14 @@ func (c *Cluster) Create() error { c.logger.Infof("pods are ready") if !(c.masterLess || c.databaseAccessDisabled()) { - err = c.createRoles() - if err != nil { + if err := c.createRoles(); err != nil { return fmt.Errorf("could not create users: %v", err) } + + if err := c.createDatabases(); err != nil { + return fmt.Errorf("could not create databases: %v", err) + } + c.logger.Infof("users have been successfully created") } else { if c.masterLess { diff --git a/pkg/cluster/pg.go b/pkg/cluster/pg.go index 29a6039c2..d7e5a9b0a 100644 --- a/pkg/cluster/pg.go +++ b/pkg/cluster/pg.go @@ -11,7 +11,8 @@ import ( "github.com/zalando-incubator/postgres-operator/pkg/util/constants" ) -var getUserSQL = `SELECT a.rolname, COALESCE(a.rolpassword, ''), a.rolsuper, a.rolinherit, +const ( + getUserSQL = `SELECT a.rolname, COALESCE(a.rolpassword, ''), a.rolsuper, a.rolinherit, a.rolcreaterole, a.rolcreatedb, a.rolcanlogin, ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m @@ -21,6 +22,10 @@ var getUserSQL = `SELECT a.rolname, COALESCE(a.rolpassword, ''), a.rolsuper, a.r WHERE a.rolname = ANY($1) ORDER BY 1;` + getDatabasesSQL = `SELECT datname, a.rolname AS owner FROM pg_database d INNER JOIN pg_authid a ON a.oid = d.datdba;` + createDatabaseSQL = `CREATE DATABASE "%s" OWNER "%s";` +) + func (c *Cluster) pgConnectionString() string { hostname := fmt.Sprintf("%s.%s.svc.cluster.local", c.Name, c.Namespace) username := c.systemUsers[constants.SuperuserKeyName].Name @@ -106,6 +111,88 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser return users, nil } +func (c *Cluster) getDatabases() (map[string]string, error) { + var ( + rows *sql.Rows + err error + ) + dbs := make(map[string]string, 0) + + if err := c.initDbConn(); err != nil { + return nil, fmt.Errorf("could not init db connection") + } + defer func() { + if err := c.closeDbConn(); err != nil { + c.logger.Errorf("could not close db connection: %v", err) + } + }() + + 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 { + err = fmt.Errorf("error when closing query cursor: %v", err2) + } + }() + + for rows.Next() { + var datname, owner string + + err := rows.Scan(&datname, &owner) + if err != nil { + return nil, fmt.Errorf("error when processing row: %v", err) + } + dbs[datname] = owner + } + + return dbs, nil +} + +func (c *Cluster) createDatabases() error { + newDbs := c.Spec.Databases + curDbs, err := c.getDatabases() + if err != nil { + return fmt.Errorf("could not get current databases: %v", err) + } + for datname := range curDbs { + delete(newDbs, datname) + } + + if len(newDbs) == 0 { + return nil + } + + if err := c.initDbConn(); err != nil { + return fmt.Errorf("could not init db connection") + } + defer func() { + if err := c.closeDbConn(); err != nil { + c.logger.Errorf("could not close db connection: %v", err) + } + }() + + for datname, owner := range newDbs { + if _, ok := c.pgUsers[owner]; !ok { + c.logger.Infof("skipping creationg of the %q database, user %q does not exist", datname, owner) + continue + } + + if !alphaNumericRegexp.MatchString(datname) { + c.logger.Infof("database %q has invalid name", datname) + continue + } + c.logger.Infof("creating database %q with owner %q", datname, owner) + + if _, err = c.pgDb.Query(fmt.Sprintf(createDatabaseSQL, datname, owner)); err != nil { + return fmt.Errorf("could not query database: %v", err) + } + } + + return nil +} + func makeUserFlags(rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool) (result []string) { if rolsuper { result = append(result, constants.RoleFlagSuperuser) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index 1e3962caf..e4ed09a9b 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -100,6 +100,7 @@ type PostgresSpec struct { MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"` Clone CloneDescription `json:"clone"` ClusterName string `json:"-"` + Databases map[string]string `json:"databases,omitempty"` } // PostgresqlList defines a list of PostgreSQL clusters.