package main import ( "fmt" "io/ioutil" "log" "net/http" "os" "github.com/labstack/echo" "github.com/rosti-cz/node-api/apps" "github.com/rosti-cz/node-api/common" docker "github.com/rosti-cz/node-api/containers" "github.com/rosti-cz/node-api/node" ) func homeHandler(c echo.Context) error { return c.Render(http.StatusOK, "index.html", templateData{ Token: config.Token, }) } func listAppsHandler(c echo.Context) error { err := gatherStates() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } applications, err := processor.List() 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") err := updateState(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) 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" app := apps.App{} err := c.Bind(&app) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } err = processor.New(app.Name, app.SSHPort, app.HTTPPort, app.Image, app.CPU, app.Memory) if err != nil { if validationError, ok := err.(apps.ValidationError); ok { return c.JSONPretty(http.StatusBadRequest, Message{Errors: validationError.Errors}, JSONIndent) } return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } if !registerOnly { container := docker.Container{ App: &app, } err = container.Create() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } err = container.Start() 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") app := apps.App{} err := c.Bind(&app) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } appPointer, err := processor.Update(name, app.SSHPort, app.HTTPPort, app.Image, app.CPU, app.Memory) if err != nil { if validationError, ok := err.(apps.ValidationError); ok { return c.JSONPretty(http.StatusBadRequest, Message{Errors: validationError.Errors}, JSONIndent) } return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } app = *appPointer container := docker.Container{ App: &app, } err = container.Destroy() if err != nil && err.Error() == "no container found" { // We don't care if the container didn't exist anyway err = nil } if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } err = container.Create() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } err = container.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 stopAppHandler(c echo.Context) error { name := c.Param("name") processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } status, err := container.Status() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } // Stop the container only when it exists if status != "no-container" { err = container.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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } status, err := container.Status() if err != nil { return err } if status == "no-container" { err = container.Create() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } err = container.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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } err = container.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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } err = container.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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } err = container.SetFileContent(sshPubKeysLocation, string(body)+"\n", "0600") 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") quickServices := &QuickServices{} err := c.Bind(quickServices) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } if quickServices.Python { err = container.SetTechnology("python", "") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } if quickServices.PHP { err = container.SetTechnology("php", "") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } if quickServices.Node { err = container.SetTechnology("node", "") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } if quickServices.Ruby { err = container.SetTechnology("ruby", "") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } if quickServices.Deno { err = container.SetTechnology("deno", "") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } if quickServices.Memcached { err = container.SetTechnology("memcached", "") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } if quickServices.Redis { err = container.SetTechnology("redis", "") 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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } err = container.Destroy() if err != nil && err.Error() == "no container found" { // We don't care if the container didn't exist anyway err = nil } if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } err = container.Create() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } err = container.Start() 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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } err = processor.AddLabel(name, 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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } err = processor.RemoveLabel(name, 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") processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } go func(app *apps.App) { container := docker.Container{ App: app, } status, err := container.Status() if err != nil { log.Println("ERROR delete application problem: " + err.Error()) } if status != "no-container" { err = container.Delete() if err != nil { log.Println("ERROR delete application problem: " + err.Error()) } } err = processor.Delete(app.Name) if err != nil { log.Println("ERROR delete application problem: " + err.Error()) } }(app) 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{}) } // Return info about the node including performance index func getNodeInfoHandler(c echo.Context) error { node, err := node.GetNodeInfo() 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 := apps.AppsProcessor{ DB: common.GetDBConnection(), } app, err := processor.Get(name) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } container := docker.Container{ App: app, } processes, err := container.GetProcessList() 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 node, err := node.GetNodeInfo() 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) processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } apps, err := processor.List() 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) }