package main import ( "fmt" "io" "io/ioutil" "log" "net/http" "os" "strings" "github.com/labstack/echo/v4" "github.com/rosti-cz/node-api/apps" "github.com/rosti-cz/node-api/common" "github.com/rosti-cz/node-api/glue" ) func homeHandler(c echo.Context) error { return c.Render(http.StatusOK, "index.html", templateData{ Token: config.Token, }) } func listAppsHandler(c echo.Context) error { processor := glue.Processor{ DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } applications, err := processor.List(false) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, applications) } // Returns one app func getAppHandler(c echo.Context) error { name := c.Param("name") fast := c.Param("fast") processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } app, err := processor.Get(fast == "1") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, app) } // Create a new app // If you add register_only=1 into query string, it won't start or create any container, just adds record into the database. func createAppHandler(c echo.Context) error { registerOnly := c.QueryParam("register_only") == "1" appTemplate := apps.App{} err := c.Bind(&appTemplate) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := glue.Processor{ AppName: appTemplate.Name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } if registerOnly { err = processor.Register(appTemplate) if err != nil && strings.Contains(err.Error(), "validation error") { return c.JSONPretty(http.StatusBadRequest, Message{Errors: []string{err.Error()}}, JSONIndent) } else if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } else { err = processor.Create(appTemplate) if err != nil && strings.Contains(err.Error(), "validation error") { return c.JSONPretty(http.StatusBadRequest, Message{Errors: []string{err.Error()}}, JSONIndent) } else if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Update existing app func updateAppHandler(c echo.Context) error { name := c.Param("name") appTemplate := apps.App{} err := c.Bind(&appTemplate) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err = processor.Update(appTemplate) if err != nil && strings.Contains(err.Error(), "validation error") { return c.JSONPretty(http.StatusBadRequest, Message{Errors: []string{err.Error()}}, JSONIndent) } else if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Stop existing app func stopAppHandler(c echo.Context) error { name := c.Param("name") processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err := processor.Stop() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Start existing app func startAppHandler(c echo.Context) error { name := c.Param("name") processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err := processor.Start() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Stop existing app func restartAppHandler(c echo.Context) error { name := c.Param("name") processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err := processor.Restart() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Set password for the app user in the container func setPasswordHandler(c echo.Context) error { name := c.Param("name") password := Password{} err := c.Bind(&password) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err = processor.SetPassword(password.Password) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Copies body of the request into /srv/.ssh/authorized_keys func setKeysHandler(c echo.Context) error { name := c.Param("name") body, err := ioutil.ReadAll(c.Request().Body) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err = processor.UpdateKeys(string(body) + "\n") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } func setServicesHandler(c echo.Context) error { name := c.Param("name") tech := &Technology{} err := c.Bind(tech) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err = processor.EnableTech(tech.Name, tech.Version) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Rebuilds existing app, it keeps the data but created the container again func rebuildAppHandler(c echo.Context) error { name := c.Param("name") processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err := processor.Rebuild() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Adds new label func addLabelHandler(c echo.Context) error { name := c.Param("name") label := apps.Label{} err := c.Bind(&label) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err = processor.AddLabel(label.Value) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Removes existing label func deleteLabelHandler(c echo.Context) error { name := c.Param("name") label := apps.Label{} err := c.Bind(&label) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err = processor.RemoveLabel(label.Value) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, Message{Message: "ok"}) } // Delete one app // This is async function. func deleteAppHandler(c echo.Context) error { name := c.Param("name") go func(name string) { processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } err := processor.Delete() if err != nil { log.Printf("Deletion of application failed: %v", err) } }(name) return c.JSON(http.StatusOK, Message{Message: "deleted"}) } // Orphans returns directories in /srv that doesn't match any hosted application func getOrphansHander(c echo.Context) error { return c.JSON(http.StatusOK, []string{}) } // Save metadata for the app func saveMetadataHandler(c echo.Context) error { name := c.Param("name") processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), SnapshotProcessor: &snapshotProcessor, DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } body, err := io.ReadAll(c.Request().Body) if err != nil { return fmt.Errorf("error reading request body: %v", err) } err = processor.SaveMetadata(string(body)) if err != nil { return fmt.Errorf("error while save metadata: %v", err.Error()) } return nil } // Return info about the node including performance index func getNodeInfoHandler(c echo.Context) error { processor := glue.Processor{ DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, NodeProcessor: &nodeProcessor, } node, err := processor.GetNode() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, node) } // Returns list of running processes (inside supervisor) func getAppProcessesHandler(c echo.Context) error { name := c.Param("name") processor := glue.Processor{ AppName: name, DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } processes, err := processor.Processes() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } return c.JSON(http.StatusOK, processes) } // Return metrics that are available for running node func metricsHandler(c echo.Context) error { var metrics string // Node indexes processor := glue.Processor{ DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, NodeProcessor: &nodeProcessor, } node, err := processor.GetNode() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } hostname, err := os.Hostname() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } metrics += fmt.Sprintf("rosti_node_index{hostname=\"%s\"} %f\n", hostname, node.Index) metrics += fmt.Sprintf("rosti_node_disk_space_index{hostname=\"%s\"} %f\n", hostname, node.DiskSpaceIndex) metrics += fmt.Sprintf("rosti_node_load1_index{hostname=\"%s\"} %f\n", hostname, node.Load1Index) metrics += fmt.Sprintf("rosti_node_load5_index{hostname=\"%s\"} %f\n", hostname, node.Load5Index) metrics += fmt.Sprintf("rosti_node_load15_index{hostname=\"%s\"} %f\n", hostname, node.Load15Index) metrics += fmt.Sprintf("rosti_node_memory_index{hostname=\"%s\"} %f\n", hostname, node.MemoryIndex) metrics += fmt.Sprintf("rosti_node_sold_memory{hostname=\"%s\"} %d\n", hostname, node.SoldMemory) if elapsedMetric != -1 { metrics += fmt.Sprintf("rosti_node_stats_time_elapsed{hostname=\"%s\"} %f\n", hostname, float64(elapsedMetric)/1000000000) } apps, err := processor.List(true) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } for _, app := range apps { metrics += fmt.Sprintf("rosti_node_app_disk_usage_bytes{hostname=\"%s\", app=\"%s\"} %d\n", hostname, app.Name, app.DiskUsageBytes) metrics += fmt.Sprintf("rosti_node_app_disk_usage_inodes{hostname=\"%s\", app=\"%s\"} %d\n", hostname, app.Name, app.DiskUsageInodes) } return c.String(http.StatusOK, metrics) }