16306: Merge branch 'master'
authorTom Clegg <tom@curii.com>
Tue, 2 Feb 2021 21:58:17 +0000 (16:58 -0500)
committerTom Clegg <tom@curii.com>
Tue, 2 Feb 2021 21:58:17 +0000 (16:58 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

1  2 
build/run-tests.sh
lib/install/deps.go

diff --combined build/run-tests.sh
index 7bd4e618dd16b75659d888bda9931a63fc040b7a,4067a37cff5bb10c9b95665700f5409d00b39fca..d6dc43416a5bc8bf8e3aab56c0a5df78c2e759e5
@@@ -244,7 -244,7 +244,7 @@@ sanity_checks() 
          || fatal "No gitolite. Try: apt-get install gitolite3"
      echo -n 'npm: '
      npm --version \
-         || fatal "No npm. Try: wget -O- https://nodejs.org/dist/v6.11.2/node-v6.11.2-linux-x64.tar.xz | sudo tar -C /usr/local -xJf - && sudo ln -s ../node-v6.11.2-linux-x64/bin/{node,npm} /usr/local/bin/"
+         || fatal "No npm. Try: wget -O- https://nodejs.org/dist/v10.23.1/node-v10.23.1-linux-x64.tar.xz | sudo tar -C /usr/local -xJf - && sudo ln -s ../node-v10.23.1-linux-x64/bin/{node,npm} /usr/local/bin/"
      echo -n 'cadaver: '
      cadaver --version | grep -w cadaver \
            || fatal "No cadaver. Try: apt-get install cadaver"
@@@ -550,7 -550,7 +550,7 @@@ setup_ruby_environment() 
          (
              export HOME=$GEMHOME
              bundlers="$(gem list --details bundler)"
 -            versions=(1.11.0 1.17.3 2.0.2)
 +            versions=(1.16.6 1.17.3 2.0.2)
              for v in ${versions[@]}; do
                  if ! echo "$bundlers" | fgrep -q "($v)"; then
                      gem install --user $(for v in ${versions[@]}; do echo bundler:${v}; done)
diff --combined lib/install/deps.go
index 8a182bcb93d33531b70a136ae9bf30b5d66efc58,6902688253c0a1a6ee55aeea5af258fc388dc2ed..6d9556f985f13bd76efbe9bf5b557e71b615f8ef
@@@ -14,8 -14,6 +14,8 @@@ import 
        "io"
        "os"
        "os/exec"
 +      "os/user"
 +      "path/filepath"
        "strconv"
        "strings"
        "syscall"
        "github.com/lib/pq"
  )
  
 -var Command cmd.Handler = installCommand{}
 +var Command cmd.Handler = &installCommand{}
  
  const devtestDatabasePassword = "insecure_arvados_test"
  
 -type installCommand struct{}
 +type installCommand struct {
 +      ClusterType    string
 +      SourcePath     string
 +      PackageVersion string
 +      EatMyData      bool
 +}
  
 -func (installCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
 +func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
        logger := ctxlog.New(stderr, "text", "info")
        ctx := ctxlog.Context(context.Background(), logger)
        ctx, cancel := context.WithCancel(ctx)
        flags := flag.NewFlagSet(prog, flag.ContinueOnError)
        flags.SetOutput(stderr)
        versionFlag := flags.Bool("version", false, "Write version information to stdout and exit 0")
 -      clusterType := flags.String("type", "production", "cluster `type`: development, test, or production")
 +      flags.StringVar(&inst.ClusterType, "type", "production", "cluster `type`: development, test, production, or package")
 +      flags.StringVar(&inst.SourcePath, "source", "/arvados", "source tree location (required for -type=package)")
 +      flags.StringVar(&inst.PackageVersion, "package-version", "0.0.0", "version string to embed in executable files")
 +      flags.BoolVar(&inst.EatMyData, "eatmydata", false, "use eatmydata to speed up install")
        err = flags.Parse(args)
        if err == flag.ErrHelp {
                err = nil
                return 2
        } else if *versionFlag {
                return cmd.Version.RunCommand(prog, args, stdin, stdout, stderr)
 +      } else if len(flags.Args()) > 0 {
 +              err = fmt.Errorf("unrecognized command line arguments: %v", flags.Args())
 +              return 2
        }
  
 -      var dev, test, prod bool
 -      switch *clusterType {
 +      var dev, test, prod, pkg bool
 +      switch inst.ClusterType {
        case "development":
                dev = true
        case "test":
                test = true
        case "production":
                prod = true
 +      case "package":
 +              pkg = true
        default:
 -              err = fmt.Errorf("invalid cluster type %q (must be 'development', 'test', or 'production')", *clusterType)
 +              err = fmt.Errorf("invalid cluster type %q (must be 'development', 'test', 'production', or 'package')", inst.ClusterType)
                return 2
        }
  
                }
        }
  
 -      if dev || test {
 -              debs := []string{
 +      if inst.EatMyData {
 +              cmd := exec.CommandContext(ctx, "apt-get", "install", "--yes", "--no-install-recommends", "eatmydata")
 +              cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive")
 +              cmd.Stdout = stdout
 +              cmd.Stderr = stderr
 +              err = cmd.Run()
 +              if err != nil {
 +                      return 1
 +              }
 +      }
 +
 +      pkgs := prodpkgs(osv)
 +
 +      if pkg {
 +              pkgs = append(pkgs,
 +                      "dpkg-dev",
 +                      "eatmydata", // install it for later steps, even if we're not using it now
 +                      "rsync",
 +              )
 +      }
 +
 +      if dev || test || pkg {
 +              pkgs = append(pkgs,
 +                      "automake",
                        "bison",
                        "bsdmainutils",
                        "build-essential",
 -                      "ca-certificates",
                        "cadaver",
                        "curl",
                        "cython3",
 -                      "daemontools", // lib/boot uses setuidgid to drop privileges when running as root
                        "default-jdk-headless",
                        "default-jre-headless",
 -                      "fuse",
                        "gettext",
 -                      "git",
 -                      "gitolite3",
 -                      "graphviz",
 -                      "haveged",
                        "iceweasel",
                        "libattr1-dev",
                        "libcrypt-ssleay-perl",
 -                      "libcrypt-ssleay-perl",
 -                      "libcurl3-gnutls",
 -                      "libcurl4-openssl-dev",
                        "libfuse-dev",
                        "libgnutls28-dev",
                        "libjson-perl",
 -                      "libjson-perl",
                        "libpam-dev",
                        "libpcre3-dev",
                        "libpq-dev",
                        "libssl-dev",
                        "libwww-perl",
                        "libxml2-dev",
 -                      "libxslt1.1",
 +                      "libxslt1-dev",
                        "linkchecker",
                        "lsof",
 +                      "make",
                        "net-tools",
 -                      "nginx",
                        "pandoc",
                        "perl-modules",
                        "pkg-config",
                        "sudo",
                        "wget",
                        "xvfb",
 -                      "zlib1g-dev",
 -              }
 +              )
                switch {
                case osv.Debian && osv.Major >= 10:
 -                      debs = append(debs, "libcurl4")
 +                      pkgs = append(pkgs, "libcurl4")
                default:
 -                      debs = append(debs, "libcurl3")
 +                      pkgs = append(pkgs, "libcurl3")
                }
 -              cmd := exec.CommandContext(ctx, "apt-get", "install", "--yes", "--no-install-recommends")
 -              cmd.Args = append(cmd.Args, debs...)
 +              cmd := exec.CommandContext(ctx, "apt-get")
 +              if inst.EatMyData {
 +                      cmd = exec.CommandContext(ctx, "eatmydata", "apt-get")
 +              }
 +              cmd.Args = append(cmd.Args, "install", "--yes", "--no-install-recommends")
 +              cmd.Args = append(cmd.Args, pkgs...)
                cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive")
                cmd.Stdout = stdout
                cmd.Stderr = stderr
        }
  
        os.Mkdir("/var/lib/arvados", 0755)
 -      rubyversion := "2.5.7"
 +      os.Mkdir("/var/lib/arvados/tmp", 0700)
 +      if prod || pkg {
 +              os.Mkdir("/var/lib/arvados/wwwtmp", 0700)
 +              u, er := user.Lookup("www-data")
 +              if er != nil {
 +                      err = fmt.Errorf("user.Lookup(%q): %w", "www-data", er)
 +                      return 1
 +              }
 +              uid, _ := strconv.Atoi(u.Uid)
 +              gid, _ := strconv.Atoi(u.Gid)
 +              err = os.Chown("/var/lib/arvados/wwwtmp", uid, gid)
 +              if err != nil {
 +                      return 1
 +              }
 +      }
 +      rubyversion := "2.7.2"
 +      rubymajorversion := rubyversion[:strings.LastIndex(rubyversion, ".")]
        if haverubyversion, err := exec.Command("/var/lib/arvados/bin/ruby", "-v").CombinedOutput(); err == nil && bytes.HasPrefix(haverubyversion, []byte("ruby "+rubyversion)) {
                logger.Print("ruby " + rubyversion + " already installed")
        } else {
 -              err = runBash(`
 -mkdir -p /var/lib/arvados/tmp
 -tmp=/var/lib/arvados/tmp/ruby-`+rubyversion+`
 -trap "rm -r ${tmp}" ERR
 -wget --progress=dot:giga -O- https://cache.ruby-lang.org/pub/ruby/2.5/ruby-`+rubyversion+`.tar.gz | tar -C /var/lib/arvados/tmp -xzf -
 -cd ${tmp}
 -./configure --disable-install-doc --prefix /var/lib/arvados
 -make -j4
 +              err = inst.runBash(`
 +tmp="$(mktemp -d)"
 +trap 'rm -r "${tmp}"' ERR EXIT
 +wget --progress=dot:giga -O- https://cache.ruby-lang.org/pub/ruby/`+rubymajorversion+`/ruby-`+rubyversion+`.tar.gz | tar -C "${tmp}" -xzf -
 +cd "${tmp}/ruby-`+rubyversion+`"
 +./configure --disable-install-static-library --enable-shared --disable-install-doc --prefix /var/lib/arvados
 +make -j8
  make install
 -/var/lib/arvados/bin/gem install bundler
 -rm -r ${tmp}
 +/var/lib/arvados/bin/gem install bundler --no-document
  `, stdout, stderr)
                if err != nil {
                        return 1
                if havegoversion, err := exec.Command("/usr/local/bin/go", "version").CombinedOutput(); err == nil && bytes.HasPrefix(havegoversion, []byte("go version go"+goversion+" ")) {
                        logger.Print("go " + goversion + " already installed")
                } else {
 -                      err = runBash(`
 +                      err = inst.runBash(`
  cd /tmp
  wget --progress=dot:giga -O- https://storage.googleapis.com/golang/go`+goversion+`.linux-amd64.tar.gz | tar -C /var/lib/arvados -xzf -
  ln -sf /var/lib/arvados/go/bin/* /usr/local/bin/
                                return 1
                        }
                }
 +      }
  
 +      if !prod && !pkg {
                pjsversion := "1.9.8"
                if havepjsversion, err := exec.Command("/usr/local/bin/phantomjs", "--version").CombinedOutput(); err == nil && string(havepjsversion) == "1.9.8\n" {
                        logger.Print("phantomjs " + pjsversion + " already installed")
                } else {
 -                      err = runBash(`
 +                      err = inst.runBash(`
  PJS=phantomjs-`+pjsversion+`-linux-x86_64
  wget --progress=dot:giga -O- https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 | tar -C /var/lib/arvados -xjf -
  ln -sf /var/lib/arvados/$PJS/bin/phantomjs /usr/local/bin/
                if havegeckoversion, err := exec.Command("/usr/local/bin/geckodriver", "--version").CombinedOutput(); err == nil && strings.Contains(string(havegeckoversion), " "+geckoversion+" ") {
                        logger.Print("geckodriver " + geckoversion + " already installed")
                } else {
 -                      err = runBash(`
 +                      err = inst.runBash(`
  GD=v`+geckoversion+`
  wget --progress=dot:giga -O- https://github.com/mozilla/geckodriver/releases/download/$GD/geckodriver-$GD-linux64.tar.gz | tar -C /var/lib/arvados/bin -xzf - geckodriver
  ln -sf /var/lib/arvados/bin/geckodriver /usr/local/bin/
                        }
                }
  
-               nodejsversion := "v8.15.1"
+               nodejsversion := "v10.23.1"
                if havenodejsversion, err := exec.Command("/usr/local/bin/node", "--version").CombinedOutput(); err == nil && string(havenodejsversion) == nodejsversion+"\n" {
                        logger.Print("nodejs " + nodejsversion + " already installed")
                } else {
 -                      err = runBash(`
 +                      err = inst.runBash(`
  NJS=`+nodejsversion+`
  wget --progress=dot:giga -O- https://nodejs.org/dist/${NJS}/node-${NJS}-linux-x64.tar.xz | sudo tar -C /var/lib/arvados -xJf -
  ln -sf /var/lib/arvados/node-${NJS}-linux-x64/bin/{node,npm} /usr/local/bin/
                if havegradleversion, err := exec.Command("/usr/local/bin/gradle", "--version").CombinedOutput(); err == nil && strings.Contains(string(havegradleversion), "Gradle "+gradleversion+"\n") {
                        logger.Print("gradle " + gradleversion + " already installed")
                } else {
 -                      err = runBash(`
 +                      err = inst.runBash(`
  G=`+gradleversion+`
 -mkdir -p /var/lib/arvados/tmp
  zip=/var/lib/arvados/tmp/gradle-${G}-bin.zip
  trap "rm ${zip}" ERR
  wget --progress=dot:giga -O${zip} https://services.gradle.org/distributions/gradle-${G}-bin.zip
@@@ -322,7 -278,7 +322,7 @@@ rm ${zip
                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)
 +                      err = inst.runBash(`sed -i 's/^# *\(`+wantlocale+`\)/\1/' /etc/locale.gen && locale-gen`, stdout, stderr)
                        if err != nil {
                                return 1
                        }
                        // 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 {
 +                      if err = inst.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
                }
        }
  
 +      if prod || pkg {
 +              // Install Rails apps to /var/lib/arvados/{railsapi,workbench1}/
 +              for dstdir, srcdir := range map[string]string{
 +                      "railsapi":   "services/api",
 +                      "workbench1": "apps/workbench",
 +              } {
 +                      fmt.Fprintf(stderr, "building %s...\n", srcdir)
 +                      cmd := exec.Command("rsync",
 +                              "-a", "--no-owner", "--no-group", "--delete-after", "--delete-excluded",
 +                              "--exclude", "/coverage",
 +                              "--exclude", "/log",
 +                              "--exclude", "/tmp",
 +                              "--exclude", "/vendor",
 +                              "--exclude", "/config/environments",
 +                              "./", "/var/lib/arvados/"+dstdir+"/")
 +                      cmd.Dir = filepath.Join(inst.SourcePath, srcdir)
 +                      cmd.Stdout = stdout
 +                      cmd.Stderr = stderr
 +                      err = cmd.Run()
 +                      if err != nil {
 +                              return 1
 +                      }
 +                      for _, cmdline := range [][]string{
 +                              {"mkdir", "-p", "log", "tmp", ".bundle", "/var/www/.gem", "/var/www/.bundle", "/var/www/.passenger"},
 +                              {"touch", "log/production.log"},
 +                              {"chown", "-R", "--from=root", "www-data:www-data", "/var/www/.gem", "/var/www/.bundle", "/var/www/.passenger", "log", "tmp", ".bundle", "Gemfile.lock", "config.ru", "config/environment.rb"},
 +                              {"sudo", "-u", "www-data", "/var/lib/arvados/bin/gem", "install", "--user", "--conservative", "--no-document", "bundler:1.16.6", "bundler:1.17.3", "bundler:2.0.2"},
 +                              {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "install", "--deployment", "--jobs", "8", "--path", "/var/www/.gem"},
 +                              {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "exec", "passenger-config", "build-native-support"},
 +                              {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "exec", "passenger-config", "install-standalone-runtime"},
 +                      } {
 +                              cmd = exec.Command(cmdline[0], cmdline[1:]...)
 +                              cmd.Dir = "/var/lib/arvados/" + dstdir
 +                              cmd.Stdout = stdout
 +                              cmd.Stderr = stderr
 +                              fmt.Fprintf(stderr, "... %s\n", cmd.Args)
 +                              err = cmd.Run()
 +                              if err != nil {
 +                                      return 1
 +                              }
 +                      }
 +                      cmd = exec.Command("sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "exec", "passenger-config", "validate-install")
 +                      cmd.Dir = "/var/lib/arvados/" + dstdir
 +                      cmd.Stdout = stdout
 +                      cmd.Stderr = stderr
 +                      err = cmd.Run()
 +                      if err != nil && !strings.Contains(err.Error(), "exit status 2") {
 +                              // Exit code 2 indicates there were warnings (like
 +                              // "other passenger installations have been detected",
 +                              // which we can't expect to avoid) but no errors.
 +                              // Other non-zero exit codes (1, 9) indicate errors.
 +                              return 1
 +                      }
 +              }
 +
 +              // Install Go programs to /var/lib/arvados/bin/
 +              for _, srcdir := range []string{
 +                      "cmd/arvados-client",
 +                      "cmd/arvados-server",
 +                      "services/arv-git-httpd",
 +                      "services/crunch-dispatch-local",
 +                      "services/crunch-dispatch-slurm",
 +                      "services/health",
 +                      "services/keep-balance",
 +                      "services/keep-web",
 +                      "services/keepproxy",
 +                      "services/keepstore",
 +                      "services/ws",
 +              } {
 +                      fmt.Fprintf(stderr, "building %s...\n", srcdir)
 +                      cmd := exec.Command("go", "install", "-ldflags", "-X git.arvados.org/arvados.git/lib/cmd.version="+inst.PackageVersion+" -X main.version="+inst.PackageVersion)
 +                      cmd.Env = append(cmd.Env, os.Environ()...)
 +                      cmd.Env = append(cmd.Env, "GOBIN=/var/lib/arvados/bin")
 +                      cmd.Dir = filepath.Join(inst.SourcePath, srcdir)
 +                      cmd.Stdout = stdout
 +                      cmd.Stderr = stderr
 +                      err = cmd.Run()
 +                      if err != nil {
 +                              return 1
 +                      }
 +              }
 +
 +              // Copy assets from source tree to /var/lib/arvados/share
 +              cmd := exec.Command("install", "-v", "-t", "/var/lib/arvados/share", filepath.Join(inst.SourcePath, "sdk/python/tests/nginx.conf"))
 +              cmd.Stdout = stdout
 +              cmd.Stderr = stderr
 +              err = cmd.Run()
 +              if err != nil {
 +                      return 1
 +              }
 +      }
 +
        return 0
  }
  
  type osversion struct {
        Debian bool
        Ubuntu bool
 +      Centos bool
        Major  int
  }
  
@@@ -572,8 -435,6 +572,8 @@@ func identifyOS() (osversion, error) 
                osv.Ubuntu = true
        case "debian":
                osv.Debian = true
 +      case "centos":
 +              osv.Centos = true
        default:
                return osv, fmt.Errorf("unsupported ID in /etc/os-release: %q", kv["ID"])
        }
@@@ -601,64 -462,10 +601,64 @@@ func waitPostgreSQLReady() error 
        }
  }
  
 -func runBash(script string, stdout, stderr io.Writer) error {
 +func (inst *installCommand) runBash(script string, stdout, stderr io.Writer) error {
        cmd := exec.Command("bash", "-")
 +      if inst.EatMyData {
 +              cmd = exec.Command("eatmydata", "bash", "-")
 +      }
        cmd.Stdin = bytes.NewBufferString("set -ex -o pipefail\n" + script)
        cmd.Stdout = stdout
        cmd.Stderr = stderr
        return cmd.Run()
  }
 +
 +func prodpkgs(osv osversion) []string {
 +      pkgs := []string{
 +              "ca-certificates",
 +              "curl",
 +              "fuse",
 +              "git",
 +              "gitolite3",
 +              "graphviz",
 +              "haveged",
 +              "libcurl3-gnutls",
 +              "libxslt1.1",
 +              "nginx",
 +              "python",
 +              "sudo",
 +      }
 +      if osv.Debian || osv.Ubuntu {
 +              if osv.Debian && osv.Major == 8 {
 +                      pkgs = append(pkgs, "libgnutls-deb0-28") // sdk/cwl
 +              } else if osv.Debian && osv.Major >= 10 || osv.Ubuntu && osv.Major >= 16 {
 +                      pkgs = append(pkgs, "python3-distutils") // sdk/cwl
 +              }
 +              return append(pkgs,
 +                      "g++",
 +                      "libcurl4-openssl-dev", // services/api
 +                      "libpq-dev",
 +                      "libpython2.7", // services/fuse
 +                      "mime-support", // keep-web
 +                      "zlib1g-dev",   // services/api
 +              )
 +      } else if osv.Centos {
 +              return append(pkgs,
 +                      "fuse-libs", // services/fuse
 +                      "gcc",
 +                      "gcc-c++",
 +                      "libcurl-devel",    // services/api
 +                      "mailcap",          // keep-web
 +                      "postgresql-devel", // services/api
 +              )
 +      } else {
 +              panic("os version not supported")
 +      }
 +}
 +
 +func ProductionDependencies() ([]string, error) {
 +      osv, err := identifyOS()
 +      if err != nil {
 +              return nil, err
 +      }
 +      return prodpkgs(osv), nil
 +}