20846: Specify nodejs version without 'v' prefix.
[arvados.git] / lib / install / deps.go
index 500d1ec7b8ff092382b13acb60ad155ba4649c50..0f8d299aa49d7d12b437728047f963ed5f67277b 100644 (file)
@@ -17,6 +17,7 @@ import (
        "os/exec"
        "os/user"
        "path/filepath"
+       "regexp"
        "runtime"
        "strconv"
        "strings"
@@ -33,25 +34,29 @@ var Command cmd.Handler = &installCommand{}
 const goversion = "1.20.6"
 
 const (
-       rubyversion             = "2.7.7"
-       bundlerversion          = "2.2.19"
-       singularityversion      = "3.10.4"
-       pjsversion              = "1.9.8"
-       geckoversion            = "0.24.0"
-       gradleversion           = "5.3.1"
-       nodejsversion           = "v12.22.12"
-       devtestDatabasePassword = "insecure_arvados_test"
+       defaultRubyVersion        = "3.2.2"
+       defaultBundlerVersion     = "2.2.19"
+       defaultSingularityVersion = "3.10.4"
+       pjsversion                = "1.9.8"
+       geckoversion              = "0.24.0"
+       gradleversion             = "5.3.1"
+       defaultNodejsVersion      = "12.22.12"
+       devtestDatabasePassword   = "insecure_arvados_test"
 )
 
 //go:embed arvados.service
 var arvadosServiceFile []byte
 
 type installCommand struct {
-       ClusterType    string
-       SourcePath     string
-       Commit         string
-       PackageVersion string
-       EatMyData      bool
+       ClusterType        string
+       SourcePath         string
+       Commit             string
+       PackageVersion     string
+       RubyVersion        string
+       BundlerVersion     string
+       SingularityVersion string
+       NodejsVersion      string
+       EatMyData          bool
 }
 
 func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
@@ -74,6 +79,10 @@ func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Read
        flags.StringVar(&inst.SourcePath, "source", "/arvados", "source tree location (required for -type=package)")
        flags.StringVar(&inst.Commit, "commit", "", "source commit `hash` to embed (blank means use 'git log' or all-zero placeholder)")
        flags.StringVar(&inst.PackageVersion, "package-version", "0.0.0", "version string to embed in executable files")
+       flags.StringVar(&inst.RubyVersion, "ruby-version", defaultRubyVersion, "Ruby `version` to install (do not override in production mode)")
+       flags.StringVar(&inst.BundlerVersion, "bundler-version", defaultBundlerVersion, "Bundler `version` to install (do not override in production mode)")
+       flags.StringVar(&inst.SingularityVersion, "singularity-version", defaultSingularityVersion, "Singularity `version` to install (do not override in production mode)")
+       flags.StringVar(&inst.NodejsVersion, "nodejs-version", defaultNodejsVersion, "Nodejs `version` to install (not applicable in production mode)")
        flags.BoolVar(&inst.EatMyData, "eatmydata", false, "use eatmydata to speed up install")
 
        if ok, code := cmd.ParseFlags(flags, prog, args, "", stderr); !ok {
@@ -110,6 +119,23 @@ func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Read
                return 1
        }
 
+       if ok, _ := regexp.MatchString(`^\d\.\d+\.\d+$`, inst.RubyVersion); !ok {
+               fmt.Fprintf(stderr, "invalid argument %q for -ruby-version\n", inst.RubyVersion)
+               return 2
+       }
+       if ok, _ := regexp.MatchString(`^\d`, inst.BundlerVersion); !ok {
+               fmt.Fprintf(stderr, "invalid argument %q for -bundler-version\n", inst.BundlerVersion)
+               return 2
+       }
+       if ok, _ := regexp.MatchString(`^\d`, inst.SingularityVersion); !ok {
+               fmt.Fprintf(stderr, "invalid argument %q for -singularity-version\n", inst.SingularityVersion)
+               return 2
+       }
+       if ok, _ := regexp.MatchString(`^\d`, inst.NodejsVersion); !ok {
+               fmt.Fprintf(stderr, "invalid argument %q for -nodejs-version\n", inst.NodejsVersion)
+               return 2
+       }
+
        osv, err := identifyOS()
        if err != nil {
                return 1
@@ -164,6 +190,7 @@ func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Read
                        "default-jre-headless",
                        "gettext",
                        "libattr1-dev",
+                       "libffi-dev",
                        "libfuse-dev",
                        "libgbm1", // cypress / workbench2 tests
                        "libgnutls28-dev",
@@ -174,6 +201,7 @@ func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Read
                        "libssl-dev",
                        "libxml2-dev",
                        "libxslt1-dev",
+                       "libyaml-dev",
                        "linkchecker",
                        "lsof",
                        "make",
@@ -216,9 +244,8 @@ func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Read
                                "gnupg")          // docker install recipe
                }
                switch {
-               case osv.Debian && osv.Major >= 11:
-                       pkgs = append(pkgs, "g++", "libcurl4", "libcurl4-openssl-dev")
-               case osv.Debian && osv.Major >= 10:
+               case osv.Debian && osv.Major >= 10,
+                       osv.Ubuntu && osv.Major >= 22:
                        pkgs = append(pkgs, "g++", "libcurl4", "libcurl4-openssl-dev")
                case osv.Debian || osv.Ubuntu:
                        pkgs = append(pkgs, "g++", "libcurl3", "libcurl3-openssl-dev")
@@ -304,18 +331,24 @@ fi
                        return 1
                }
        }
-       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")
+       rubyminorversion := inst.RubyVersion[:strings.LastIndex(inst.RubyVersion, ".")]
+       if haverubyversion, err := exec.Command("/var/lib/arvados/bin/ruby", "-v").CombinedOutput(); err == nil && bytes.HasPrefix(haverubyversion, []byte("ruby "+inst.RubyVersion)) {
+               logger.Print("ruby " + inst.RubyVersion + " already installed")
        } else {
                err = inst.runBash(`
+rubyversion="`+inst.RubyVersion+`"
+rubyminorversion="`+rubyminorversion+`"
 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+`"
+wget --progress=dot:giga -O- "https://cache.ruby-lang.org/pub/ruby/$rubyminorversion/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
+rm -f /var/lib/arvados/bin/erb
 make install
+if [[ "$rubyversion" > "3" ]]; then
+  /var/lib/arvados/bin/gem update --no-document --system 3.4.21
+fi
 /var/lib/arvados/bin/gem install bundler --no-document
 `, stdout, stderr)
                if err != nil {
@@ -383,11 +416,11 @@ rm ${zip}
                        }
                }
 
-               if havesingularityversion, err := exec.Command("/var/lib/arvados/bin/singularity", "--version").CombinedOutput(); err == nil && strings.Contains(string(havesingularityversion), singularityversion) {
-                       logger.Print("singularity " + singularityversion + " already installed")
+               if havesingularityversion, err := exec.Command("/var/lib/arvados/bin/singularity", "--version").CombinedOutput(); err == nil && strings.Contains(string(havesingularityversion), inst.SingularityVersion) {
+                       logger.Print("singularity " + inst.SingularityVersion + " already installed")
                } else if dev || test {
                        err = inst.runBash(`
-S=`+singularityversion+`
+S=`+inst.SingularityVersion+`
 tmp=/var/lib/arvados/tmp/singularity
 trap "rm -r ${tmp}" ERR EXIT
 cd /var/lib/arvados/tmp
@@ -533,11 +566,11 @@ setcap "cap_sys_admin+pei cap_sys_chroot+pei" /var/lib/arvados/bin/nsenter
        }
 
        if !prod {
-               if havenodejsversion, err := exec.Command("/usr/local/bin/node", "--version").CombinedOutput(); err == nil && string(havenodejsversion) == nodejsversion+"\n" {
-                       logger.Print("nodejs " + nodejsversion + " already installed")
+               if havenodejsversion, err := exec.Command("/usr/local/bin/node", "--version").CombinedOutput(); err == nil && string(havenodejsversion) == "v"+inst.NodejsVersion+"\n" {
+                       logger.Print("nodejs " + inst.NodejsVersion + " already installed")
                } else {
                        err = inst.runBash(`
-NJS=`+nodejsversion+`
+NJS=v`+inst.NodejsVersion+`
 rm -rf /var/lib/arvados/node-*-linux-x64
 wget --progress=dot:giga -O- https://nodejs.org/dist/${NJS}/node-${NJS}-linux-x64.tar.xz | sudo tar -C /var/lib/arvados -xJf -
 ln -sfv /var/lib/arvados/node-${NJS}-linux-x64/bin/{node,npm} /usr/local/bin/
@@ -552,7 +585,7 @@ ln -sfv /var/lib/arvados/node-${NJS}-linux-x64/bin/{node,npm} /usr/local/bin/
                } else {
                        err = inst.runBash(`
 npm install -g yarn
-ln -sfv /var/lib/arvados/node-`+nodejsversion+`-linux-x64/bin/{yarn,yarnpkg} /usr/local/bin/
+ln -sfv /var/lib/arvados/node-v`+inst.NodejsVersion+`-linux-x64/bin/{yarn,yarnpkg} /usr/local/bin/
 `, stdout, stderr)
                        if err != nil {
                                return 1
@@ -615,89 +648,95 @@ pip3 install --no-cache-dir 'setuptools>=68' 'pip>=20'
 export ARVADOS_BUILDING_VERSION="`+inst.PackageVersion+`"
 for src in "`+inst.SourcePath+`/sdk/python" "`+inst.SourcePath+`/services/fuse"; do
   rsync -a --delete-after "$src/" "$tmp/"
-  cd "$tmp"
-  python3 setup.py install
-  cd ..
+  env -C "$tmp" python3 setup.py build
+  pip3 install "$tmp"
   rm -rf "$tmp"
 done
 `, stdout, stderr); err != nil {
                        return 1
                }
 
-               // Install Rails apps to /var/lib/arvados/{railsapi,workbench1}/
-               for dstdir, srcdir := range map[string]string{
-                       "railsapi":   "services/api",
-                       "workbench1": "apps/workbench",
+               // Install RailsAPI to /var/lib/arvados/railsapi/
+               fmt.Fprintln(stderr, "building railsapi...")
+               cmd = exec.Command("rsync",
+                       "-a", "--no-owner", "--no-group", "--delete-after", "--delete-excluded",
+                       "--exclude", "/coverage",
+                       "--exclude", "/log",
+                       "--exclude", "/node_modules",
+                       "--exclude", "/tmp",
+                       "--exclude", "/public/assets",
+                       "--exclude", "/vendor",
+                       "--exclude", "/config/environments",
+                       "./", "/var/lib/arvados/railsapi/")
+               cmd.Dir = filepath.Join(inst.SourcePath, "services", "api")
+               cmd.Stdout = stdout
+               cmd.Stderr = stderr
+               err = cmd.Run()
+               if err != nil {
+                       return 1
+               }
+               for _, cmdline := range [][]string{
+                       {"mkdir", "-p", "log", "public/assets", "tmp", "vendor", ".bundle", "/var/www/.bundle", "/var/www/.gem", "/var/www/.npm", "/var/www/.passenger"},
+                       {"touch", "log/production.log"},
+                       {"chown", "-R", "--from=root", "www-data:www-data", "/var/www/.bundle", "/var/www/.gem", "/var/www/.npm", "/var/www/.passenger", "log", "tmp", "vendor", ".bundle", "Gemfile.lock", "config.ru", "config/environment.rb"},
+                       {"sudo", "-u", "www-data", "/var/lib/arvados/bin/gem", "install", "--user", "--conservative", "--no-document", "bundler:" + inst.BundlerVersion},
+                       {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "deployment", "true"},
+                       {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "path", "/var/www/.gem"},
+                       {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "without", "development test diagnostics performance"},
+                       {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "install", "--jobs", fmt.Sprintf("%d", runtime.NumCPU())},
+
+                       {"chown", "www-data:www-data", ".", "public/assets"},
+                       // {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "system", "true"},
+                       {"sudo", "-u", "www-data", "ARVADOS_CONFIG=none", "RAILS_GROUPS=assets", "RAILS_ENV=production", "PATH=/var/lib/arvados/bin:" + os.Getenv("PATH"), "/var/lib/arvados/bin/bundle", "exec", "rake", "npm:install"},
+                       {"sudo", "-u", "www-data", "ARVADOS_CONFIG=none", "RAILS_GROUPS=assets", "RAILS_ENV=production", "PATH=/var/lib/arvados/bin:" + os.Getenv("PATH"), "/var/lib/arvados/bin/bundle", "exec", "rake", "assets:precompile"},
+                       {"chown", "root:root", "."},
+                       {"chown", "-R", "root:root", "public/assets", "vendor"},
+
+                       {"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"},
                } {
-                       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", "/node_modules",
-                               "--exclude", "/tmp",
-                               "--exclude", "/public/assets",
-                               "--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", "public/assets", "tmp", "vendor", ".bundle", "/var/www/.bundle", "/var/www/.gem", "/var/www/.npm", "/var/www/.passenger"},
-                               {"touch", "log/production.log"},
-                               {"chown", "-R", "--from=root", "www-data:www-data", "/var/www/.bundle", "/var/www/.gem", "/var/www/.npm", "/var/www/.passenger", "log", "tmp", "vendor", ".bundle", "Gemfile.lock", "config.ru", "config/environment.rb"},
-                               {"sudo", "-u", "www-data", "/var/lib/arvados/bin/gem", "install", "--user", "--conservative", "--no-document", "bundler:" + bundlerversion},
-                               {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "deployment", "true"},
-                               {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "path", "/var/www/.gem"},
-                               {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "without", "development test diagnostics performance"},
-                               {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "install", "--jobs", fmt.Sprintf("%d", runtime.NumCPU())},
-
-                               {"chown", "www-data:www-data", ".", "public/assets"},
-                               // {"sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "config", "set", "--local", "system", "true"},
-                               {"sudo", "-u", "www-data", "ARVADOS_CONFIG=none", "RAILS_GROUPS=assets", "RAILS_ENV=production", "PATH=/var/lib/arvados/bin:" + os.Getenv("PATH"), "/var/lib/arvados/bin/bundle", "exec", "rake", "npm:install"},
-                               {"sudo", "-u", "www-data", "ARVADOS_CONFIG=none", "RAILS_GROUPS=assets", "RAILS_ENV=production", "PATH=/var/lib/arvados/bin:" + os.Getenv("PATH"), "/var/lib/arvados/bin/bundle", "exec", "rake", "assets:precompile"},
-                               {"chown", "root:root", "."},
-                               {"chown", "-R", "root:root", "public/assets", "vendor"},
-
-                               {"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"},
-                       } {
-                               if cmdline[len(cmdline)-2] == "rake" && dstdir != "workbench1" {
-                                       continue
-                               }
-                               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
-                               }
+                       if cmdline[len(cmdline)-2] == "rake" {
+                               continue
                        }
-                       cmd = exec.Command("sudo", "-u", "www-data", "/var/lib/arvados/bin/bundle", "exec", "passenger-config", "validate-install")
-                       cmd.Dir = "/var/lib/arvados/" + dstdir
+                       cmd = exec.Command(cmdline[0], cmdline[1:]...)
+                       cmd.Dir = "/var/lib/arvados/railsapi"
                        cmd.Stdout = stdout
                        cmd.Stderr = stderr
+                       fmt.Fprintf(stderr, "... %s\n", cmd.Args)
                        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.
+                       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/railsapi"
+               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 workbench2 app to /var/lib/arvados/workbench2/
+               // Install workbench2 app to
+               // /var/lib/arvados/workbench2/.
+               //
+               // We copy the source tree from the (possibly
+               // readonly) source tree into a temp dir because `yarn
+               // build` writes to {source-tree}/build/. When we
+               // upgrade to react-scripts >= 4.0.2 we may be able to
+               // build from the source dir and write directly to the
+               // final destination (using
+               // YARN_INSTALL_STATE_PATH=/dev/null
+               // BUILD_PATH=/var/lib/arvados/workbench2) instead of
+               // using two rsync steps here.
                if err = inst.runBash(`
 src="`+inst.SourcePath+`/services/workbench2"
 tmp=/var/lib/arvados/tmp/workbench2
+trap "rm -r ${tmp}" ERR EXIT
 dst=/var/lib/arvados/workbench2
 rsync -a --delete-after "$src/" "$tmp/"
 env -C "$tmp" VERSION="`+inst.PackageVersion+`" BUILD_NUMBER=1 GIT_COMMIT="`+inst.Commit[:9]+`" yarn build