Merge branch 'master' into 16811-public-favs
[arvados.git] / lib / install / deps.go
index b08dd33bf91462a5c1972daaaa4e66055a3c0fa1..93a0ce452bf7c59f0483b1b84eacb6cac3416c8d 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bufio"
        "bytes"
        "context"
+       "errors"
        "flag"
        "fmt"
        "io"
@@ -65,10 +66,15 @@ func (installCommand) RunCommand(prog string, args []string, stdin io.Reader, st
        case "production":
                prod = true
        default:
-               err = fmt.Errorf("cluster type must be 'development', 'test', or 'production'")
+               err = fmt.Errorf("invalid cluster type %q (must be 'development', 'test', or 'production')", *clusterType)
                return 2
        }
 
+       if prod {
+               err = errors.New("production install is not yet implemented")
+               return 1
+       }
+
        osv, err := identifyOS()
        if err != nil {
                return 1
@@ -95,9 +101,10 @@ func (installCommand) RunCommand(prog string, args []string, stdin io.Reader, st
                        "bison",
                        "bsdmainutils",
                        "build-essential",
+                       "ca-certificates",
                        "cadaver",
                        "curl",
-                       "cython",
+                       "cython3",
                        "daemontools", // lib/boot uses setuidgid to drop privileges when running as root
                        "default-jdk-headless",
                        "default-jre-headless",
@@ -120,7 +127,6 @@ func (installCommand) RunCommand(prog string, args []string, stdin io.Reader, st
                        "libpam-dev",
                        "libpcre3-dev",
                        "libpq-dev",
-                       "libpython2.7-dev",
                        "libreadline-dev",
                        "libssl-dev",
                        "libwww-perl",
@@ -135,13 +141,12 @@ func (installCommand) RunCommand(prog string, args []string, stdin io.Reader, st
                        "pkg-config",
                        "postgresql",
                        "postgresql-contrib",
-                       "python",
                        "python3-dev",
-                       "python-epydoc",
                        "r-base",
                        "r-cran-testthat",
                        "sudo",
-                       "virtualenv",
+                       "python3-virtualenv",
+                       "python3-venv",
                        "wget",
                        "xvfb",
                        "zlib1g-dev",
@@ -261,8 +266,11 @@ rm ${zip}
                        }
                }
 
+               // The entry in /etc/locale.gen is "en_US.UTF-8"; once
+               // it's installed, locale -a reports it as
+               // "en_US.utf8".
                wantlocale := "en_US.UTF-8"
-               if havelocales, err := exec.Command("locale", "-a").CombinedOutput(); err == nil && bytes.Contains(havelocales, []byte(wantlocale+"\n")) {
+               if havelocales, err := exec.Command("locale", "-a").CombinedOutput(); err == nil && bytes.Contains(havelocales, []byte(strings.Replace(wantlocale+"\n", "UTF-", "utf", 1))) {
                        logger.Print("locale " + wantlocale + " already installed")
                } else {
                        err = runBash(`sed -i 's/^# *\(`+wantlocale+`\)/\1/' /etc/locale.gen && locale-gen`, stdout, stderr)
@@ -301,19 +309,12 @@ rm ${zip}
                        }
                        defer func() {
                                cmd.Process.Signal(syscall.SIGTERM)
-                               logger.Infof("sent SIGTERM; waiting for postgres to shut down")
+                               logger.Info("sent SIGTERM; waiting for postgres to shut down")
                                cmd.Wait()
                        }()
-                       for deadline := time.Now().Add(10 * time.Second); ; {
-                               output, err2 := exec.Command("pg_isready").CombinedOutput()
-                               if err2 == nil {
-                                       break
-                               } else if time.Now().After(deadline) {
-                                       err = fmt.Errorf("timed out waiting for pg_isready (%q)", output)
-                                       return 1
-                               } else {
-                                       time.Sleep(time.Second)
-                               }
+                       err = waitPostgreSQLReady()
+                       if err != nil {
+                               return 1
                        }
                }
 
@@ -324,6 +325,51 @@ rm ${zip}
                        // might never have been run.
                }
 
+               var needcoll []string
+               // If the en_US.UTF-8 locale wasn't installed when
+               // postgresql initdb ran, it needs to be added
+               // explicitly before we can use it in our test suite.
+               for _, collname := range []string{"en_US", "en_US.UTF-8"} {
+                       cmd := exec.Command("sudo", "-u", "postgres", "psql", "-t", "-c", "SELECT 1 FROM pg_catalog.pg_collation WHERE collname='"+collname+"' AND collcollate IN ('en_US.UTF-8', 'en_US.utf8')")
+                       cmd.Dir = "/"
+                       out, err2 := cmd.CombinedOutput()
+                       if err != nil {
+                               err = fmt.Errorf("error while checking postgresql collations: %s", err2)
+                               return 1
+                       }
+                       if strings.Contains(string(out), "1") {
+                               logger.Infof("postgresql supports collation %s", collname)
+                       } else {
+                               needcoll = append(needcoll, collname)
+                       }
+               }
+               if len(needcoll) > 0 && os.Getpid() != 1 {
+                       // In order for the CREATE COLLATION statement
+                       // below to work, the locale must have existed
+                       // when PostgreSQL started up. If we're
+                       // running as init, we must have started
+                       // PostgreSQL ourselves after installing the
+                       // locales. Otherwise, it might need a
+                       // restart, so we attempt to restart it with
+                       // systemd.
+                       if err = runBash(`sudo systemctl restart postgresql`, stdout, stderr); err != nil {
+                               logger.Warn("`systemctl restart postgresql` failed; hoping postgresql does not need to be restarted")
+                       } else if err = waitPostgreSQLReady(); err != nil {
+                               return 1
+                       }
+               }
+               for _, collname := range needcoll {
+                       cmd := exec.Command("sudo", "-u", "postgres", "psql", "-c", "CREATE COLLATION \""+collname+"\" (LOCALE = \"en_US.UTF-8\")")
+                       cmd.Stdout = stdout
+                       cmd.Stderr = stderr
+                       cmd.Dir = "/"
+                       err = cmd.Run()
+                       if err != nil {
+                               err = fmt.Errorf("error adding postgresql collation %s: %s", collname, err)
+                               return 1
+                       }
+               }
+
                withstuff := "WITH LOGIN SUPERUSER ENCRYPTED PASSWORD " + pq.QuoteLiteral(devtestDatabasePassword)
                cmd := exec.Command("sudo", "-u", "postgres", "psql", "-c", "ALTER ROLE arvados "+withstuff)
                cmd.Dir = "/"
@@ -393,11 +439,24 @@ func identifyOS() (osversion, error) {
        }
        osv.Major, err = strconv.Atoi(vstr)
        if err != nil {
-               return osv, fmt.Errorf("incomprehensible VERSION_ID in /etc/os/release: %q", kv["VERSION_ID"])
+               return osv, fmt.Errorf("incomprehensible VERSION_ID in /etc/os-release: %q", kv["VERSION_ID"])
        }
        return osv, nil
 }
 
+func waitPostgreSQLReady() error {
+       for deadline := time.Now().Add(10 * time.Second); ; {
+               output, err := exec.Command("pg_isready").CombinedOutput()
+               if err == nil {
+                       return nil
+               } else if time.Now().After(deadline) {
+                       return fmt.Errorf("timed out waiting for pg_isready (%q)", output)
+               } else {
+                       time.Sleep(time.Second)
+               }
+       }
+}
+
 func runBash(script string, stdout, stderr io.Writer) error {
        cmd := exec.Command("bash", "-")
        cmd.Stdin = bytes.NewBufferString("set -ex -o pipefail\n" + script)