13497: Abort startup if Rails API cannot be found.
[arvados.git] / lib / service / cmd.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 // package service provides a cmd.Handler that brings up a system service.
6 package service
7
8 import (
9         "flag"
10         "fmt"
11         "io"
12         "net/http"
13
14         "git.curoverse.com/arvados.git/lib/cmd"
15         "git.curoverse.com/arvados.git/sdk/go/arvados"
16         "git.curoverse.com/arvados.git/sdk/go/httpserver"
17         "github.com/Sirupsen/logrus"
18         "github.com/coreos/go-systemd/daemon"
19 )
20
21 type Handler interface {
22         http.Handler
23         CheckHealth() error
24 }
25
26 type NewHandlerFunc func(*arvados.Cluster, *arvados.SystemNode) Handler
27
28 type command struct {
29         newHandler NewHandlerFunc
30         svcName    arvados.ServiceName
31 }
32
33 // Command returns a cmd.Handler that loads site config, calls
34 // newHandler with the current cluster and node configs, and brings up
35 // an http server with the returned handler.
36 //
37 // The handler is wrapped with server middleware (adding X-Request-ID
38 // headers, logging requests/responses, etc).
39 func Command(svcName arvados.ServiceName, newHandler NewHandlerFunc) cmd.Handler {
40         return &command{
41                 newHandler: newHandler,
42                 svcName:    svcName,
43         }
44 }
45
46 func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
47         log := logrus.New()
48         log.Formatter = &logrus.JSONFormatter{
49                 TimestampFormat: rfc3339NanoFixed,
50         }
51         log.Out = stderr
52
53         var err error
54         defer func() {
55                 if err != nil {
56                         log.WithError(err).Info("exiting")
57                 }
58         }()
59         flags := flag.NewFlagSet("", flag.ContinueOnError)
60         flags.SetOutput(stderr)
61         configFile := flags.String("config", arvados.DefaultConfigFile, "Site configuration `file`")
62         hostName := flags.String("host", "", "Host profile `name` to use in SystemNodes config (if blank, use hostname reported by OS)")
63         err = flags.Parse(args)
64         if err == flag.ErrHelp {
65                 err = nil
66                 return 0
67         } else if err != nil {
68                 return 2
69         }
70         cfg, err := arvados.GetConfig(*configFile)
71         if err != nil {
72                 return 1
73         }
74         cluster, err := cfg.GetCluster("")
75         if err != nil {
76                 return 1
77         }
78         node, err := cluster.GetSystemNode(*hostName)
79         if err != nil {
80                 return 1
81         }
82         listen := node.ServicePorts()[c.svcName]
83         if listen == "" {
84                 err = fmt.Errorf("configuration does not enable the %s service on this host", c.svcName)
85                 return 1
86         }
87         handler := c.newHandler(cluster, node)
88         if err = handler.CheckHealth(); err != nil {
89                 return 1
90         }
91         srv := &httpserver.Server{
92                 Server: http.Server{
93                         Handler: httpserver.AddRequestIDs(httpserver.LogRequests(log, handler)),
94                 },
95                 Addr: listen,
96         }
97         err = srv.Start()
98         if err != nil {
99                 return 1
100         }
101         log.WithFields(logrus.Fields{
102                 "Listen":  srv.Addr,
103                 "Service": c.svcName,
104         }).Info("listening")
105         if _, err := daemon.SdNotify(false, "READY=1"); err != nil {
106                 log.WithError(err).Errorf("error notifying init daemon")
107         }
108         err = srv.Wait()
109         if err != nil {
110                 return 1
111         }
112         return 0
113 }
114
115 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"