X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/7abc7ca38954acd4eaa53c9280504e06a76b8d71..9c58422ecfd63291972fae7a00e3a4f683c2d1b4:/lib/boot/postgresql.go diff --git a/lib/boot/postgresql.go b/lib/boot/postgresql.go index 86328e110c..d105b0b623 100644 --- a/lib/boot/postgresql.go +++ b/lib/boot/postgresql.go @@ -8,9 +8,12 @@ import ( "bytes" "context" "database/sql" + "fmt" "os" "os/exec" + "os/user" "path/filepath" + "strconv" "strings" "time" @@ -18,83 +21,142 @@ import ( "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 + if super.ClusterType == "production" { + return nil + } + + iamroot := false + if u, err := user.Current(); err != nil { + return fmt.Errorf("user.Current(): %w", 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, runOptions{output: buf}, "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"} + opts := runOptions{} + 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 + opts.user = "postgres" + } + err = super.RunProgram(ctx, super.tempdir, opts, prog, args...) + if err != nil { + return err + } + + err = super.RunProgram(ctx, super.tempdir, runOptions{}, "cp", "server.crt", "server.key", datadir) + if err != nil { + return err + } + if iamroot { + err = super.RunProgram(ctx, super.tempdir, runOptions{}, "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 + "-h", super.cluster.PostgreSQL.Connection["host"], + "-p", super.cluster.PostgreSQL.Connection["port"], + } + opts := runOptions{} + if iamroot { + opts.user = "postgres" + } + fail(super.RunProgram(ctx, super.tempdir, opts, 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 }