]> git.arvados.org - arvados.git/blob - lib/boot/postgresql.go
Merge branch '23009-multiselect-bug' into main. Closes #23009
[arvados.git] / lib / boot / postgresql.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package boot
6
7 import (
8         "bytes"
9         "context"
10         "database/sql"
11         "fmt"
12         "os"
13         "os/user"
14         "path/filepath"
15         "strconv"
16         "strings"
17         "time"
18
19         "git.arvados.org/arvados.git/sdk/go/arvados"
20         "github.com/lib/pq"
21 )
22
23 // Run a postgresql server in a private data directory. Set up a db
24 // user, database, and TCP listener that match the supervisor's
25 // configured database connection info.
26 type runPostgreSQL struct{}
27
28 func (runPostgreSQL) String() string {
29         return "postgresql"
30 }
31
32 func (runPostgreSQL) Run(ctx context.Context, fail func(error), super *Supervisor) error {
33         err := super.wait(ctx, createCertificates{})
34         if err != nil {
35                 return err
36         }
37
38         if super.ClusterType == "production" {
39                 return nil
40         }
41
42         postgresUser, err := user.Current()
43         iamroot := postgresUser.Uid == "0"
44         if err != nil {
45                 return fmt.Errorf("user.Current(): %w", err)
46         } else if iamroot {
47                 postgresUser, err = user.Lookup("postgres")
48                 if err != nil {
49                         return fmt.Errorf("user.Lookup(\"postgres\"): %s", err)
50                 }
51         }
52
53         buf := bytes.NewBuffer(nil)
54         err = super.RunProgram(ctx, super.tempdir, runOptions{output: buf}, "pg_config", "--bindir")
55         if err != nil {
56                 return err
57         }
58         bindir := strings.TrimSpace(buf.String())
59
60         datadir := filepath.Join(super.tempdir, "pgdata")
61         err = os.Mkdir(datadir, 0700)
62         if err != nil {
63                 return err
64         }
65         prog, args := filepath.Join(bindir, "initdb"), []string{"-D", datadir, "-E", "utf8"}
66         opts := runOptions{}
67         opts.env = append(opts.env,
68                 "PGHOST="+super.cluster.PostgreSQL.Connection["host"],
69                 "PGPORT="+super.cluster.PostgreSQL.Connection["port"],
70                 "PGUSER="+postgresUser.Username,
71                 "PGDATABASE=",
72                 "PGPASSFILE=",
73         )
74         if iamroot {
75                 postgresUID, err := strconv.Atoi(postgresUser.Uid)
76                 if err != nil {
77                         return fmt.Errorf("user.Lookup(\"postgres\"): non-numeric uid?: %q", postgresUser.Uid)
78                 }
79                 postgresGid, err := strconv.Atoi(postgresUser.Gid)
80                 if err != nil {
81                         return fmt.Errorf("user.Lookup(\"postgres\"): non-numeric gid?: %q", postgresUser.Gid)
82                 }
83                 err = os.Chown(super.tempdir, 0, postgresGid)
84                 if err != nil {
85                         return err
86                 }
87                 err = os.Chmod(super.tempdir, 0710)
88                 if err != nil {
89                         return err
90                 }
91                 err = os.Chown(datadir, postgresUID, 0)
92                 if err != nil {
93                         return err
94                 }
95                 opts.user = "postgres"
96         }
97         err = super.RunProgram(ctx, super.tempdir, opts, prog, args...)
98         if err != nil {
99                 return err
100         }
101
102         err = super.RunProgram(ctx, super.tempdir, runOptions{}, "cp", "server.crt", "server.key", datadir)
103         if err != nil {
104                 return err
105         }
106         if iamroot {
107                 err = super.RunProgram(ctx, super.tempdir, runOptions{}, "chown", "postgres", datadir+"/server.crt", datadir+"/server.key")
108                 if err != nil {
109                         return err
110                 }
111         }
112
113         super.waitShutdown.Add(1)
114         go func() {
115                 defer super.waitShutdown.Done()
116                 prog, args := filepath.Join(bindir, "postgres"), []string{
117                         "-l",          // enable ssl
118                         "-D", datadir, // data dir
119                         "-k", datadir, // socket dir
120                         "-h", super.cluster.PostgreSQL.Connection["host"],
121                         "-p", super.cluster.PostgreSQL.Connection["port"],
122                 }
123                 fail(super.RunProgram(ctx, super.tempdir, opts, prog, args...))
124         }()
125
126         for {
127                 if ctx.Err() != nil {
128                         return ctx.Err()
129                 }
130                 err := super.RunProgram(ctx, super.tempdir, opts, "pg_isready", "--timeout=10")
131                 if err == nil {
132                         break
133                 }
134                 time.Sleep(time.Second / 2)
135         }
136         pgconn := arvados.PostgreSQLConnection{
137                 "host":   datadir,
138                 "port":   super.cluster.PostgreSQL.Connection["port"],
139                 "user":   postgresUser.Username,
140                 "dbname": "postgres",
141         }
142         db, err := sql.Open("postgres", pgconn.String())
143         if err != nil {
144                 return fmt.Errorf("db open failed: %s", err)
145         }
146         defer db.Close()
147         conn, err := db.Conn(ctx)
148         if err != nil {
149                 return fmt.Errorf("db conn failed: %s", err)
150         }
151         defer conn.Close()
152         _, err = conn.ExecContext(ctx, `CREATE USER `+pq.QuoteIdentifier(super.cluster.PostgreSQL.Connection["user"])+` WITH SUPERUSER ENCRYPTED PASSWORD `+pq.QuoteLiteral(super.cluster.PostgreSQL.Connection["password"]))
153         if err != nil {
154                 return fmt.Errorf("createuser failed: %s", err)
155         }
156         _, err = conn.ExecContext(ctx, `CREATE DATABASE `+pq.QuoteIdentifier(super.cluster.PostgreSQL.Connection["dbname"])+` WITH TEMPLATE template0 ENCODING 'utf8'`)
157         if err != nil {
158                 return fmt.Errorf("createdb failed: %s", err)
159         }
160         return nil
161 }