Merge branch '20889-installer-fixes'. Closes #20889
[arvados.git] / cmd / arvados-server / cmd.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "context"
9         "encoding/json"
10         "fmt"
11         "io"
12         "net/http"
13         "os"
14         "path"
15         "path/filepath"
16         "strings"
17
18         "git.arvados.org/arvados.git/lib/boot"
19         "git.arvados.org/arvados.git/lib/cloud/cloudtest"
20         "git.arvados.org/arvados.git/lib/cmd"
21         "git.arvados.org/arvados.git/lib/config"
22         "git.arvados.org/arvados.git/lib/controller"
23         "git.arvados.org/arvados.git/lib/crunchrun"
24         "git.arvados.org/arvados.git/lib/crunchstat"
25         "git.arvados.org/arvados.git/lib/dispatchcloud"
26         "git.arvados.org/arvados.git/lib/install"
27         "git.arvados.org/arvados.git/lib/lsf"
28         "git.arvados.org/arvados.git/lib/recovercollection"
29         "git.arvados.org/arvados.git/lib/service"
30         "git.arvados.org/arvados.git/sdk/go/arvados"
31         "git.arvados.org/arvados.git/sdk/go/health"
32         dispatchslurm "git.arvados.org/arvados.git/services/crunch-dispatch-slurm"
33         "git.arvados.org/arvados.git/services/githttpd"
34         keepbalance "git.arvados.org/arvados.git/services/keep-balance"
35         keepweb "git.arvados.org/arvados.git/services/keep-web"
36         "git.arvados.org/arvados.git/services/keepproxy"
37         "git.arvados.org/arvados.git/services/keepstore"
38         "git.arvados.org/arvados.git/services/ws"
39         "github.com/prometheus/client_golang/prometheus"
40 )
41
42 var (
43         handler = cmd.Multi(map[string]cmd.Handler{
44                 "version":   cmd.Version,
45                 "-version":  cmd.Version,
46                 "--version": cmd.Version,
47
48                 "boot":               boot.Command,
49                 "check":              health.CheckCommand,
50                 "cloudtest":          cloudtest.Command,
51                 "config-check":       config.CheckCommand,
52                 "config-defaults":    config.DumpDefaultsCommand,
53                 "config-dump":        config.DumpCommand,
54                 "controller":         controller.Command,
55                 "crunch-run":         crunchrun.Command,
56                 "crunchstat":         crunchstat.Command,
57                 "dispatch-cloud":     dispatchcloud.Command,
58                 "dispatch-lsf":       lsf.DispatchCommand,
59                 "dispatch-slurm":     dispatchslurm.Command,
60                 "git-httpd":          githttpd.Command,
61                 "health":             healthCommand,
62                 "install":            install.Command,
63                 "init":               install.InitCommand,
64                 "keep-balance":       keepbalance.Command,
65                 "keep-web":           keepweb.Command,
66                 "keepproxy":          keepproxy.Command,
67                 "keepstore":          keepstore.Command,
68                 "recover-collection": recovercollection.Command,
69                 "workbench2":         wb2command{},
70                 "ws":                 ws.Command,
71         })
72 )
73
74 func main() {
75         os.Exit(handler.RunCommand(os.Args[0], os.Args[1:], os.Stdin, os.Stdout, os.Stderr))
76 }
77
78 type wb2command struct{}
79
80 func (wb2command) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
81         if len(args) != 3 {
82                 fmt.Fprintf(stderr, "usage: %s api-host listen-addr app-dir\n", prog)
83                 return 1
84         }
85         configJSON, err := json.Marshal(map[string]string{"API_HOST": args[0]})
86         if err != nil {
87                 fmt.Fprintf(stderr, "json.Marshal: %s\n", err)
88                 return 1
89         }
90         servefs := http.FileServer(http.Dir(args[2]))
91         mux := http.NewServeMux()
92         mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
93                 for _, ent := range strings.Split(req.URL.Path, "/") {
94                         if ent == ".." {
95                                 http.Error(w, "invalid URL path", http.StatusBadRequest)
96                                 return
97                         }
98                 }
99                 fnm := filepath.Join(args[2], filepath.FromSlash(path.Clean("/"+req.URL.Path)))
100                 if _, err := os.Stat(fnm); os.IsNotExist(err) {
101                         req.URL.Path = "/"
102                 }
103                 servefs.ServeHTTP(w, req)
104         }))
105         mux.HandleFunc("/config.json", func(w http.ResponseWriter, _ *http.Request) {
106                 w.Write(configJSON)
107         })
108         mux.HandleFunc("/_health/ping", func(w http.ResponseWriter, _ *http.Request) {
109                 io.WriteString(w, `{"health":"OK"}`)
110         })
111         err = http.ListenAndServe(args[1], mux)
112         if err != nil {
113                 fmt.Fprintln(stderr, err.Error())
114                 return 1
115         }
116         return 0
117 }
118
119 var healthCommand cmd.Handler = service.Command(arvados.ServiceNameHealth, func(ctx context.Context, cluster *arvados.Cluster, _ string, reg *prometheus.Registry) service.Handler {
120         mClockSkew := prometheus.NewGauge(prometheus.GaugeOpts{
121                 Namespace: "arvados",
122                 Subsystem: "health",
123                 Name:      "clock_skew_seconds",
124                 Help:      "Clock skew observed in most recent health check",
125         })
126         reg.MustRegister(mClockSkew)
127         return &health.Aggregator{
128                 Cluster:         cluster,
129                 MetricClockSkew: mClockSkew,
130         }
131 })