From ad705f142435691d61828150f39589b3cae68431 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Thu, 3 Feb 2022 11:35:42 +0100 Subject: [PATCH] Cleanup and restructure --- routes.go | 77 ++++++++++-- runtimeSetup.go | 124 +++++++++++++++++++ server.go | 313 ------------------------------------------------ 3 files changed, 191 insertions(+), 323 deletions(-) create mode 100644 runtimeSetup.go diff --git a/routes.go b/routes.go index 31b646f..9d83b62 100644 --- a/routes.go +++ b/routes.go @@ -2,10 +2,13 @@ package main import ( "net/http" + "net/url" + "strconv" "strings" "github.com/ClusterCockpit/cc-backend/auth" "github.com/ClusterCockpit/cc-backend/config" + "github.com/ClusterCockpit/cc-backend/schema" "github.com/ClusterCockpit/cc-backend/templates" "github.com/gorilla/mux" ) @@ -20,10 +23,59 @@ type Route struct { Setup func(i InfoType, r *http.Request) InfoType } +func buildFilterPresets(query url.Values) map[string]interface{} { + filterPresets := map[string]interface{}{} + + if query.Get("cluster") != "" { + filterPresets["cluster"] = query.Get("cluster") + } + if query.Get("partition") != "" { + filterPresets["partition"] = query.Get("partition") + } + if query.Get("project") != "" { + filterPresets["project"] = query.Get("project") + filterPresets["projectMatch"] = "eq" + } + if query.Get("state") != "" && schema.JobState(query.Get("state")).Valid() { + filterPresets["state"] = query.Get("state") + } + if rawtags, ok := query["tag"]; ok { + tags := make([]int, len(rawtags)) + for i, tid := range rawtags { + var err error + tags[i], err = strconv.Atoi(tid) + if err != nil { + tags[i] = -1 + } + } + filterPresets["tags"] = tags + } + if query.Get("numNodes") != "" { + parts := strings.Split(query.Get("numNodes"), "-") + if len(parts) == 2 { + a, e1 := strconv.Atoi(parts[0]) + b, e2 := strconv.Atoi(parts[1]) + if e1 == nil && e2 == nil { + filterPresets["numNodes"] = map[string]int{"from": a, "to": b} + } + } + } + if query.Get("jobId") != "" { + filterPresets["jobId"] = query.Get("jobId") + } + if query.Get("arrayJobId") != "" { + if num, err := strconv.Atoi(query.Get("arrayJobId")); err == nil { + filterPresets["arrayJobId"] = num + } + } + + return filterPresets +} + func setupRoutes(router *mux.Router, routes []Route) { for _, route := range routes { - _route := route - router.HandleFunc(_route.Route, func(rw http.ResponseWriter, r *http.Request) { + route := route + router.HandleFunc(route.Route, func(rw http.ResponseWriter, r *http.Request) { conf, err := config.GetUIConfig(r) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) @@ -42,17 +94,22 @@ func setupRoutes(router *mux.Router, routes []Route) { infos["admin"] = false } - infos = _route.Setup(infos, r) + infos = route.Setup(infos, r) if id, ok := infos["id"]; ok { - _route.Title = strings.Replace(_route.Title, "", id.(string), 1) + route.Title = strings.Replace(route.Title, "", id.(string), 1) } - templates.Render(rw, r, _route.Template, &templates.Page{ - Title: _route.Title, - Config: conf, - Infos: infos, - FilterPresets: buildFilterPresets(r.URL.Query()), - }) + page := templates.Page{ + Title: route.Title, + Config: conf, + Infos: infos, + } + + if route.Filter { + page.FilterPresets = buildFilterPresets(r.URL.Query()) + } + + templates.Render(rw, r, route.Template, &page) }) } } diff --git a/runtimeSetup.go b/runtimeSetup.go new file mode 100644 index 0000000..070cf30 --- /dev/null +++ b/runtimeSetup.go @@ -0,0 +1,124 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "os" + "os/exec" + "os/user" + "strconv" + "strings" + "syscall" +) + +func loadEnv(file string) error { + f, err := os.Open(file) + if err != nil { + return err + } + + defer f.Close() + s := bufio.NewScanner(bufio.NewReader(f)) + for s.Scan() { + line := s.Text() + if strings.HasPrefix(line, "#") || len(line) == 0 { + continue + } + + if strings.Contains(line, "#") { + return errors.New("'#' are only supported at the start of a line") + } + + line = strings.TrimPrefix(line, "export ") + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("unsupported line: %#v", line) + } + + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + if strings.HasPrefix(val, "\"") { + if !strings.HasSuffix(val, "\"") { + return fmt.Errorf("unsupported line: %#v", line) + } + + runes := []rune(val[1 : len(val)-1]) + sb := strings.Builder{} + for i := 0; i < len(runes); i++ { + if runes[i] == '\\' { + i++ + switch runes[i] { + case 'n': + sb.WriteRune('\n') + case 'r': + sb.WriteRune('\r') + case 't': + sb.WriteRune('\t') + case '"': + sb.WriteRune('"') + default: + return fmt.Errorf("unsupprorted escape sequence in quoted string: backslash %#v", runes[i]) + } + continue + } + sb.WriteRune(runes[i]) + } + + val = sb.String() + } + + os.Setenv(key, val) + } + + return s.Err() +} + +func dropPrivileges() error { + if programConfig.Group != "" { + g, err := user.LookupGroup(programConfig.Group) + if err != nil { + return err + } + + gid, _ := strconv.Atoi(g.Gid) + if err := syscall.Setgid(gid); err != nil { + return err + } + } + + if programConfig.User != "" { + u, err := user.Lookup(programConfig.User) + if err != nil { + return err + } + + uid, _ := strconv.Atoi(u.Uid) + if err := syscall.Setuid(uid); err != nil { + return err + } + } + + return nil +} + +// If started via systemd, inform systemd that we are running: +// https://www.freedesktop.org/software/systemd/man/sd_notify.html +func systemdNotifiy(ready bool, status string) { + if os.Getenv("NOTIFY_SOCKET") == "" { + // Not started using systemd + return + } + + args := []string{fmt.Sprintf("--pid=%d", os.Getpid())} + if ready { + args = append(args, "--ready") + } + + if status != "" { + args = append(args, fmt.Sprintf("--status=%s", status)) + } + + cmd := exec.Command("systemd-notify", args...) + cmd.Run() // errors ignored on purpose, there is not much to do anyways. +} diff --git a/server.go b/server.go index 212ecbe..9b1e7f5 100644 --- a/server.go +++ b/server.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "context" "crypto/tls" "encoding/json" @@ -11,12 +10,8 @@ import ( "io" "net" "net/http" - "net/url" "os" - "os/exec" "os/signal" - "os/user" - "strconv" "strings" "sync" "syscall" @@ -31,7 +26,6 @@ import ( "github.com/ClusterCockpit/cc-backend/graph/generated" "github.com/ClusterCockpit/cc-backend/log" "github.com/ClusterCockpit/cc-backend/metricdata" - "github.com/ClusterCockpit/cc-backend/schema" "github.com/ClusterCockpit/cc-backend/templates" "github.com/gorilla/handlers" "github.com/gorilla/mux" @@ -455,310 +449,3 @@ func main() { wg.Wait() log.Print("Gracefull shutdown completed!") } - -func buildFilterPresets(query url.Values) map[string]interface{} { - filterPresets := map[string]interface{}{} - - if query.Get("cluster") != "" { - filterPresets["cluster"] = query.Get("cluster") - } - if query.Get("partition") != "" { - filterPresets["partition"] = query.Get("partition") - } - if query.Get("project") != "" { - filterPresets["project"] = query.Get("project") - filterPresets["projectMatch"] = "eq" - } - if query.Get("state") != "" && schema.JobState(query.Get("state")).Valid() { - filterPresets["state"] = query.Get("state") - } - if rawtags, ok := query["tag"]; ok { - tags := make([]int, len(rawtags)) - for i, tid := range rawtags { - var err error - tags[i], err = strconv.Atoi(tid) - if err != nil { - tags[i] = -1 - } - } - filterPresets["tags"] = tags - } - if query.Get("numNodes") != "" { - parts := strings.Split(query.Get("numNodes"), "-") - if len(parts) == 2 { - a, e1 := strconv.Atoi(parts[0]) - b, e2 := strconv.Atoi(parts[1]) - if e1 == nil && e2 == nil { - filterPresets["numNodes"] = map[string]int{"from": a, "to": b} - } - } - } - if query.Get("jobId") != "" { - filterPresets["jobId"] = query.Get("jobId") - } - if query.Get("arrayJobId") != "" { - if num, err := strconv.Atoi(query.Get("arrayJobId")); err == nil { - filterPresets["arrayJobId"] = num - } - } - - return filterPresets -} - -// func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) { - -// router.HandleFunc("/monitoring/jobs/", func(rw http.ResponseWriter, r *http.Request) { -// conf, infos, err := prepareRoute(r) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusInternalServerError) -// return -// } - -// templates.Render(rw, r, "monitoring/jobs.tmpl", &templates.Page{ -// Title: "Jobs - ClusterCockpit", -// Config: conf, -// Infos: infos, -// FilterPresets: buildFilterPresets(r.URL.Query()), -// }) -// }) - -// router.HandleFunc("/monitoring/job/{id:[0-9]+}", func(rw http.ResponseWriter, r *http.Request) { -// conf, infos, err := prepareRoute(r) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusInternalServerError) -// return -// } - -// id := mux.Vars(r)["id"] -// job, err := resolver.Query().Job(r.Context(), id) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusNotFound) -// return -// } - -// infos["id"] = id -// infos["jobId"] = job.JobID -// infos["clusterId"] = job.Cluster - -// templates.Render(rw, r, "monitoring/job.tmpl", &templates.Page{ -// Title: fmt.Sprintf("Job %d - ClusterCockpit", job.JobID), -// Config: conf, -// Infos: infos, -// }) -// }) - -// router.HandleFunc("/monitoring/users/", func(rw http.ResponseWriter, r *http.Request) { -// conf, infos, err := prepareRoute(r) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusInternalServerError) -// return -// } - -// infos["listType"] = "USER" - -// templates.Render(rw, r, "monitoring/list.tmpl", &templates.Page{ -// Title: "Users - ClusterCockpit", -// Config: conf, -// FilterPresets: buildFilterPresets(r.URL.Query()), -// Infos: infos, -// }) -// }) - -// router.HandleFunc("/monitoring/projects/", func(rw http.ResponseWriter, r *http.Request) { -// conf, infos, err := prepareRoute(r) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusInternalServerError) -// return -// } - -// infos["listType"] = "PROJECT" - -// templates.Render(rw, r, "monitoring/list.tmpl", &templates.Page{ -// Title: "Projects - ClusterCockpit", -// Config: conf, -// FilterPresets: buildFilterPresets(r.URL.Query()), -// Infos: infos, -// }) -// }) - -// router.HandleFunc("/monitoring/user/{id}", func(rw http.ResponseWriter, r *http.Request) { -// conf, infos, err := prepareRoute(r) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusInternalServerError) -// return -// } - -// id := mux.Vars(r)["id"] -// // TODO: One could check if the user exists, but that would be unhelpfull if authentication -// // is disabled or the user does not exist but has started jobs. -// infos["username"] = id - -// templates.Render(rw, r, "monitoring/user.tmpl", &templates.Page{ -// Title: fmt.Sprintf("User %s - ClusterCockpit", id), -// Config: conf, -// Infos: infos, -// FilterPresets: buildFilterPresets(r.URL.Query()), -// }) -// }) - -// router.HandleFunc("/monitoring/systems/", func(rw http.ResponseWriter, r *http.Request) { -// // TODO: List all clusters? -// http.Redirect(rw, r, "/", http.StatusTemporaryRedirect) -// }) - -// router.HandleFunc("/monitoring/systems/{cluster}", func(rw http.ResponseWriter, r *http.Request) { -// conf, infos, err := prepareRoute(r) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusInternalServerError) -// return -// } - -// vars := mux.Vars(r) -// infos["cluster"] = vars["cluster"] -// from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to") -// if from != "" || to != "" { -// infos["from"] = from -// infos["to"] = to -// } - -// templates.Render(rw, r, "monitoring/systems.tmpl", &templates.Page{ -// Title: fmt.Sprintf("Cluster %s - ClusterCockpit", vars["cluster"]), -// Config: conf, -// Infos: infos, -// }) -// }) - -// router.HandleFunc("/monitoring/node/{cluster}/{hostname}", func(rw http.ResponseWriter, r *http.Request) { -// conf, infos, err := prepareRoute(r) -// if err != nil { -// http.Error(rw, err.Error(), http.StatusInternalServerError) -// return -// } - -// vars := mux.Vars(r) -// infos["cluster"] = vars["cluster"] -// infos["hostname"] = vars["hostname"] -// from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to") -// if from != "" || to != "" { -// infos["from"] = from -// infos["to"] = to -// } - -// templates.Render(rw, r, "monitoring/node.tmpl", &templates.Page{ -// Title: fmt.Sprintf("Host %s - ClusterCockpit", vars["hostname"]), -// Config: conf, -// Infos: infos, -// }) -// }) -// } - -func loadEnv(file string) error { - f, err := os.Open(file) - if err != nil { - return err - } - - defer f.Close() - s := bufio.NewScanner(bufio.NewReader(f)) - for s.Scan() { - line := s.Text() - if strings.HasPrefix(line, "#") || len(line) == 0 { - continue - } - - if strings.Contains(line, "#") { - return errors.New("'#' are only supported at the start of a line") - } - - line = strings.TrimPrefix(line, "export ") - parts := strings.SplitN(line, "=", 2) - if len(parts) != 2 { - return fmt.Errorf("unsupported line: %#v", line) - } - - key := strings.TrimSpace(parts[0]) - val := strings.TrimSpace(parts[1]) - if strings.HasPrefix(val, "\"") { - if !strings.HasSuffix(val, "\"") { - return fmt.Errorf("unsupported line: %#v", line) - } - - runes := []rune(val[1 : len(val)-1]) - sb := strings.Builder{} - for i := 0; i < len(runes); i++ { - if runes[i] == '\\' { - i++ - switch runes[i] { - case 'n': - sb.WriteRune('\n') - case 'r': - sb.WriteRune('\r') - case 't': - sb.WriteRune('\t') - case '"': - sb.WriteRune('"') - default: - return fmt.Errorf("unsupprorted escape sequence in quoted string: backslash %#v", runes[i]) - } - continue - } - sb.WriteRune(runes[i]) - } - - val = sb.String() - } - - os.Setenv(key, val) - } - - return s.Err() -} - -func dropPrivileges() error { - if programConfig.Group != "" { - g, err := user.LookupGroup(programConfig.Group) - if err != nil { - return err - } - - gid, _ := strconv.Atoi(g.Gid) - if err := syscall.Setgid(gid); err != nil { - return err - } - } - - if programConfig.User != "" { - u, err := user.Lookup(programConfig.User) - if err != nil { - return err - } - - uid, _ := strconv.Atoi(u.Uid) - if err := syscall.Setuid(uid); err != nil { - return err - } - } - - return nil -} - -// If started via systemd, inform systemd that we are running: -// https://www.freedesktop.org/software/systemd/man/sd_notify.html -func systemdNotifiy(ready bool, status string) { - if os.Getenv("NOTIFY_SOCKET") == "" { - // Not started using systemd - return - } - - args := []string{fmt.Sprintf("--pid=%d", os.Getpid())} - if ready { - args = append(args, "--ready") - } - - if status != "" { - args = append(args, fmt.Sprintf("--status=%s", status)) - } - - cmd := exec.Command("systemd-notify", args...) - cmd.Run() // errors ignored on purpose, there is not much to do anyways. -}