add consul task
[arvados.git] / services / boot / server.go
1 package main
2
3 import (
4         "encoding/json"
5         "flag"
6         "log"
7         "net/http"
8         "os"
9         "strconv"
10         "time"
11
12         "git.curoverse.com/arvados.git/sdk/go/config"
13 )
14
15 const defaultCfgPath = "/etc/arvados/boot/boot.yml"
16
17 var cfg Config
18
19 func main() {
20         cfgPath := flag.String("config", defaultCfgPath, "`path` to config file")
21         flag.Parse()
22
23         if err := config.LoadFile(&cfg, *cfgPath); os.IsNotExist(err) && *cfgPath == defaultCfgPath {
24                 log.Printf("WARNING: No config file specified or found, starting fresh!")
25         } else if err != nil {
26                 log.Fatal(err)
27         }
28         cfg.SetDefaults()
29         go func() {
30                 log.Printf("starting server at %s", cfg.WebListen)
31                 log.Fatal(http.ListenAndServe(cfg.WebListen, stack(logger, apiOrAssets)))
32         }()
33         go func() {
34                 ticker := time.NewTicker(5 * time.Second)
35                 for {
36                         runTasks(&cfg, ctlTasks)
37                         <-ticker.C
38                 }
39         }()
40         <-(chan struct{})(nil)
41 }
42
43 type middleware func(http.Handler) http.Handler
44
45 var notFound = http.NotFoundHandler()
46
47 // returns a handler that implements a stack of middlewares.
48 func stack(m ...middleware) http.Handler {
49         if len(m) == 0 {
50                 return notFound
51         }
52         return m[0](stack(m[1:]...))
53 }
54
55 // logs each request.
56 func logger(next http.Handler) http.Handler {
57         return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
58                 t := time.Now()
59                 next.ServeHTTP(w, r)
60                 log.Printf("%.6f %q %q %q", time.Since(t).Seconds(), r.RemoteAddr, r.Method, r.URL.Path)
61         })
62 }
63
64 // dispatches /api/ to the API stack, everything else to the static
65 // assets stack.
66 func apiOrAssets(next http.Handler) http.Handler {
67         mux := http.NewServeMux()
68         mux.Handle("/api/", stack(apiHeaders, apiRoutes))
69         mux.Handle("/", http.FileServer(assetFS()))
70         return mux
71 }
72
73 // adds response headers suitable for API responses
74 func apiHeaders(next http.Handler) http.Handler {
75         return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76                 w.Header().Set("Content-Type", "application/json")
77                 next.ServeHTTP(w, r)
78         })
79 }
80
81 // dispatches API routes
82 func apiRoutes(http.Handler) http.Handler {
83         mux := http.NewServeMux()
84         mux.HandleFunc("/api/ping", func(w http.ResponseWriter, r *http.Request) {
85                 json.NewEncoder(w).Encode(map[string]interface{}{"time": time.Now().UTC()})
86         })
87         mux.HandleFunc("/api/tasks/ctl", func(w http.ResponseWriter, r *http.Request) {
88                 timeout := time.Minute
89                 if v, err := strconv.ParseInt(r.FormValue("timeout"), 10, 64); err == nil {
90                         timeout = time.Duration(v) * time.Second
91                 }
92                 if v, err := strconv.ParseInt(r.FormValue("newerThan"), 10, 64); err == nil {
93                         TaskState.Wait(version(v), timeout, r.Context())
94                 }
95                 rep, v := report(ctlTasks)
96                 json.NewEncoder(w).Encode(map[string]interface{}{
97                         "Version": v,
98                         "Tasks":   rep,
99                 })
100         })
101         mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
102                 w.WriteHeader(http.StatusNotFound)
103                 json.NewEncoder(w).Encode(map[string]string{"error": "not found"})
104         })
105         return mux
106 }