"bytes"
"context"
"database/sql"
+ "fmt"
"os"
"os/exec"
+ "os/user"
"path/filepath"
+ "strconv"
"strings"
"time"
"github.com/lib/pq"
)
-func runPostgres(ctx context.Context, boot *Booter, ready chan<- bool) error {
- buf := bytes.NewBuffer(nil)
- err := boot.RunProgram(ctx, boot.tempdir, buf, nil, "pg_config", "--bindir")
+// Run a postgresql server in a private data directory. Set up a db
+// user, database, and TCP listener that match the supervisor's
+// configured database connection info.
+type runPostgreSQL struct{}
+
+func (runPostgreSQL) String() string {
+ return "postgresql"
+}
+
+func (runPostgreSQL) Run(ctx context.Context, fail func(error), super *Supervisor) error {
+ err := super.wait(ctx, createCertificates{})
if err != nil {
return err
}
- datadir := filepath.Join(boot.tempdir, "pgdata")
- err = os.Mkdir(datadir, 0755)
- if err != nil {
- return err
+ iamroot := false
+ if u, err := user.Current(); err != nil {
+ return fmt.Errorf("user.Current(): %s", err)
+ } else if u.Uid == "0" {
+ iamroot = true
}
- bindir := strings.TrimSpace(buf.String())
- err = boot.RunProgram(ctx, boot.tempdir, nil, nil, filepath.Join(bindir, "initdb"), "-D", datadir)
+ buf := bytes.NewBuffer(nil)
+ err = super.RunProgram(ctx, super.tempdir, buf, nil, "pg_config", "--bindir")
if err != nil {
return err
}
+ bindir := strings.TrimSpace(buf.String())
- err = boot.RunProgram(ctx, boot.tempdir, nil, nil, "cp", "server.crt", "server.key", datadir)
+ datadir := filepath.Join(super.tempdir, "pgdata")
+ err = os.Mkdir(datadir, 0700)
if err != nil {
return err
}
-
- port := boot.cluster.PostgreSQL.Connection["port"]
-
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- go func() {
- for {
- if ctx.Err() != nil {
- return
- }
- if exec.CommandContext(ctx, "pg_isready", "--timeout=10", "--host="+boot.cluster.PostgreSQL.Connection["host"], "--port="+port).Run() == nil {
- break
- }
- time.Sleep(time.Second / 2)
+ prog, args := filepath.Join(bindir, "initdb"), []string{"-D", datadir, "-E", "utf8"}
+ if iamroot {
+ postgresUser, err := user.Lookup("postgres")
+ if err != nil {
+ return fmt.Errorf("user.Lookup(\"postgres\"): %s", err)
+ }
+ postgresUid, err := strconv.Atoi(postgresUser.Uid)
+ if err != nil {
+ return fmt.Errorf("user.Lookup(\"postgres\"): non-numeric uid?: %q", postgresUser.Uid)
}
- db, err := sql.Open("postgres", arvados.PostgreSQLConnection{
- "host": datadir,
- "port": port,
- "dbname": "postgres",
- }.String())
+ postgresGid, err := strconv.Atoi(postgresUser.Gid)
if err != nil {
- boot.logger.WithError(err).Error("db open failed")
- cancel()
- return
+ return fmt.Errorf("user.Lookup(\"postgres\"): non-numeric gid?: %q", postgresUser.Gid)
}
- defer db.Close()
- conn, err := db.Conn(ctx)
+ err = os.Chown(super.tempdir, 0, postgresGid)
if err != nil {
- boot.logger.WithError(err).Error("db conn failed")
- cancel()
- return
+ return err
}
- defer conn.Close()
- _, err = conn.ExecContext(ctx, `CREATE USER `+pq.QuoteIdentifier(boot.cluster.PostgreSQL.Connection["user"])+` WITH SUPERUSER ENCRYPTED PASSWORD `+pq.QuoteLiteral(boot.cluster.PostgreSQL.Connection["password"]))
+ err = os.Chmod(super.tempdir, 0710)
if err != nil {
- boot.logger.WithError(err).Error("createuser failed")
- cancel()
- return
+ return err
}
- _, err = conn.ExecContext(ctx, `CREATE DATABASE `+pq.QuoteIdentifier(boot.cluster.PostgreSQL.Connection["dbname"]))
+ err = os.Chown(datadir, postgresUid, 0)
if err != nil {
- boot.logger.WithError(err).Error("createdb failed")
- cancel()
- return
+ return err
}
- close(ready)
- return
+ // We can't use "sudo -u" here because it creates an
+ // intermediate process that interferes with our
+ // ability to reliably kill postgres. The setuidgid
+ // program just calls exec without forking, so it
+ // doesn't have this problem.
+ args = append([]string{"postgres", prog}, args...)
+ prog = "setuidgid"
+ }
+ err = super.RunProgram(ctx, super.tempdir, nil, nil, prog, args...)
+ if err != nil {
+ return err
+ }
+
+ err = super.RunProgram(ctx, super.tempdir, nil, nil, "cp", "server.crt", "server.key", datadir)
+ if err != nil {
+ return err
+ }
+ if iamroot {
+ err = super.RunProgram(ctx, super.tempdir, nil, nil, "chown", "postgres", datadir+"/server.crt", datadir+"/server.key")
+ if err != nil {
+ return err
+ }
+ }
+
+ port := super.cluster.PostgreSQL.Connection["port"]
+
+ super.waitShutdown.Add(1)
+ go func() {
+ defer super.waitShutdown.Done()
+ prog, args := filepath.Join(bindir, "postgres"), []string{
+ "-l", // enable ssl
+ "-D", datadir, // data dir
+ "-k", datadir, // socket dir
+ "-p", super.cluster.PostgreSQL.Connection["port"],
+ }
+ if iamroot {
+ args = append([]string{"postgres", prog}, args...)
+ prog = "setuidgid"
+ }
+ fail(super.RunProgram(ctx, super.tempdir, nil, nil, prog, args...))
}()
- return boot.RunProgram(ctx, boot.tempdir, nil, nil, filepath.Join(bindir, "postgres"),
- "-l", // enable ssl
- "-D", datadir, // data dir
- "-k", datadir, // socket dir
- "-p", boot.cluster.PostgreSQL.Connection["port"],
- )
+ for {
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ if exec.CommandContext(ctx, "pg_isready", "--timeout=10", "--host="+super.cluster.PostgreSQL.Connection["host"], "--port="+port).Run() == nil {
+ break
+ }
+ time.Sleep(time.Second / 2)
+ }
+ pgconn := arvados.PostgreSQLConnection{
+ "host": datadir,
+ "port": port,
+ "dbname": "postgres",
+ }
+ if iamroot {
+ pgconn["user"] = "postgres"
+ }
+ db, err := sql.Open("postgres", pgconn.String())
+ if err != nil {
+ return fmt.Errorf("db open failed: %s", err)
+ }
+ defer db.Close()
+ conn, err := db.Conn(ctx)
+ if err != nil {
+ return fmt.Errorf("db conn failed: %s", err)
+ }
+ defer conn.Close()
+ _, err = conn.ExecContext(ctx, `CREATE USER `+pq.QuoteIdentifier(super.cluster.PostgreSQL.Connection["user"])+` WITH SUPERUSER ENCRYPTED PASSWORD `+pq.QuoteLiteral(super.cluster.PostgreSQL.Connection["password"]))
+ if err != nil {
+ return fmt.Errorf("createuser failed: %s", err)
+ }
+ _, err = conn.ExecContext(ctx, `CREATE DATABASE `+pq.QuoteIdentifier(super.cluster.PostgreSQL.Connection["dbname"])+` WITH TEMPLATE template0 ENCODING 'utf8'`)
+ if err != nil {
+ return fmt.Errorf("createdb failed: %s", err)
+ }
+ return nil
}