15954: Skip database.yml when running under arvados-server boot.
[arvados.git] / lib / boot / passenger.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         "fmt"
11         "os"
12         "path/filepath"
13         "strings"
14         "sync"
15
16         "git.arvados.org/arvados.git/sdk/go/arvados"
17 )
18
19 // Don't trust "passenger-config" (or "bundle install") to handle
20 // concurrent installs.
21 var passengerInstallMutex sync.Mutex
22
23 var railsEnv = []string{
24         "ARVADOS_RAILS_LOG_TO_STDOUT=1",
25         "ARVADOS_CONFIG_NOLEGACY=1", // don't load database.yml from source tree
26 }
27
28 // Install a Rails application's dependencies, including phusion
29 // passenger.
30 type installPassenger struct {
31         src     string
32         depends []supervisedTask
33 }
34
35 func (runner installPassenger) String() string {
36         return "installPassenger:" + runner.src
37 }
38
39 func (runner installPassenger) Run(ctx context.Context, fail func(error), super *Supervisor) error {
40         err := super.wait(ctx, runner.depends...)
41         if err != nil {
42                 return err
43         }
44
45         passengerInstallMutex.Lock()
46         defer passengerInstallMutex.Unlock()
47
48         var buf bytes.Buffer
49         err = super.RunProgram(ctx, runner.src, &buf, nil, "gem", "list", "--details", "bundler")
50         if err != nil {
51                 return err
52         }
53         for _, version := range []string{"1.11.0", "1.17.3", "2.0.2"} {
54                 if !strings.Contains(buf.String(), "("+version+")") {
55                         err = super.RunProgram(ctx, runner.src, nil, nil, "gem", "install", "--user", "bundler:1.11", "bundler:1.17.3", "bundler:2.0.2")
56                         if err != nil {
57                                 return err
58                         }
59                         break
60                 }
61         }
62         err = super.RunProgram(ctx, runner.src, nil, nil, "bundle", "install", "--jobs", "4", "--path", filepath.Join(os.Getenv("HOME"), ".gem"))
63         if err != nil {
64                 return err
65         }
66         err = super.RunProgram(ctx, runner.src, nil, nil, "bundle", "exec", "passenger-config", "build-native-support")
67         if err != nil {
68                 return err
69         }
70         err = super.RunProgram(ctx, runner.src, nil, nil, "bundle", "exec", "passenger-config", "install-standalone-runtime")
71         if err != nil {
72                 return err
73         }
74         err = super.RunProgram(ctx, runner.src, nil, nil, "bundle", "exec", "passenger-config", "validate-install")
75         if err != nil && !strings.Contains(err.Error(), "exit status 2") {
76                 // Exit code 2 indicates there were warnings (like
77                 // "other passenger installations have been detected",
78                 // which we can't expect to avoid) but no errors.
79                 // Other non-zero exit codes (1, 9) indicate errors.
80                 return err
81         }
82         return nil
83 }
84
85 type runPassenger struct {
86         src     string
87         svc     arvados.Service
88         depends []supervisedTask
89 }
90
91 func (runner runPassenger) String() string {
92         return "runPassenger:" + runner.src
93 }
94
95 func (runner runPassenger) Run(ctx context.Context, fail func(error), super *Supervisor) error {
96         err := super.wait(ctx, runner.depends...)
97         if err != nil {
98                 return err
99         }
100         port, err := internalPort(runner.svc)
101         if err != nil {
102                 return fmt.Errorf("bug: no internalPort for %q: %v (%#v)", runner, err, runner.svc)
103         }
104         loglevel := "4"
105         if lvl, ok := map[string]string{
106                 "debug":   "5",
107                 "info":    "4",
108                 "warn":    "2",
109                 "warning": "2",
110                 "error":   "1",
111                 "fatal":   "0",
112                 "panic":   "0",
113         }[super.cluster.SystemLogs.LogLevel]; ok {
114                 loglevel = lvl
115         }
116         super.waitShutdown.Add(1)
117         go func() {
118                 defer super.waitShutdown.Done()
119                 err = super.RunProgram(ctx, runner.src, nil, railsEnv, "bundle", "exec",
120                         "passenger", "start",
121                         "-p", port,
122                         "--log-file", "/dev/stderr",
123                         "--log-level", loglevel,
124                         "--no-friendly-error-pages",
125                         "--pid-file", filepath.Join(super.tempdir, "passenger."+strings.Replace(runner.src, "/", "_", -1)+".pid"))
126                 fail(err)
127         }()
128         return nil
129 }