diff --git a/docker/docker.go b/docker/docker.go index 39a3f61..ea76b5e 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "io/ioutil" "log" @@ -364,9 +365,9 @@ func (d *Driver) Create(name string, image string, volumePath string, HTTPPort i } // Exec runs command cmd with stdin if it's not empty. -func (d *Driver) Exec(name string, cmd []string, stdin string, env []string) error { +func (d *Driver) Exec(name string, cmd []string, stdin string, env []string, attachStdout bool) (*[]byte, error) { if len(cmd) == 0 { - return errors.New("cmd needs at least one string in the slice") + return &[]byte{}, errors.New("cmd needs at least one string in the slice") } ctx := context.Background() @@ -379,43 +380,55 @@ func (d *Driver) Exec(name string, cmd []string, stdin string, env []string) err log.Println("Command running in " + name) cli, err := d.getClient() if err != nil { - return err + return &[]byte{}, err } containerID, err := d.nameToID(name) if err != nil { - return err + return &[]byte{}, err } execOpts := types.ExecConfig{ AttachStdin: stdinEnabled, - AttachStdout: false, + AttachStdout: attachStdout, AttachStderr: false, + Tty: attachStdout, Cmd: cmd, } resp, err := cli.ContainerExecCreate(ctx, containerID, execOpts) if err != nil { - return err + return &[]byte{}, err } - respAttach, err := cli.ContainerExecAttach(ctx, resp.ID, types.ExecConfig{}) + respAttach, err := cli.ContainerExecAttach(ctx, resp.ID, types.ExecConfig{ + AttachStdin: stdinEnabled, + AttachStdout: attachStdout, + AttachStderr: false, + Tty: attachStdout, + }) if err != nil { - return err + return &[]byte{}, err } defer respAttach.Close() err = cli.ContainerExecStart(ctx, resp.ID, types.ExecStartCheck{}) if err != nil { - return err + return &[]byte{}, err } if stdinEnabled { _, err = respAttach.Conn.Write([]byte(stdin)) if err != nil { - return err + return &[]byte{}, err } } - return nil + stdouterr := []byte{} + if attachStdout { + stdouterr, err = ioutil.ReadAll(respAttach.Reader) + fmt.Println(string(stdouterr)) + } + + return &stdouterr, err } diff --git a/docker/types.go b/docker/types.go index 5a647d7..4a57615 100644 --- a/docker/types.go +++ b/docker/types.go @@ -3,6 +3,7 @@ package docker import ( "log" "path" + "strings" "github.com/rosti-cz/node-api/apps" ) @@ -11,6 +12,12 @@ import ( const appUsername = "app" const passwordFile = "/srv/.rosti" +// Process contains info about background application usually running in supervisor +type Process struct { + Name string + State string +} + // Container extends App struct from App type Container struct { App *apps.App `json:"app"` @@ -171,12 +178,12 @@ func (c *Container) Delete() error { func (c *Container) SetPassword(password string) error { driver := c.getDriver() - err := driver.Exec(c.App.Name, []string{"chpasswd"}, appUsername+":"+password, []string{}) + _, err := driver.Exec(c.App.Name, []string{"chpasswd"}, appUsername+":"+password, []string{}, false) if err != nil { return err } - err = driver.Exec(c.App.Name, []string{"tee", passwordFile}, password, []string{}) + _, err = driver.Exec(c.App.Name, []string{"tee", passwordFile}, password, []string{}, false) if err != nil { return err } @@ -191,27 +198,27 @@ func (c *Container) SetFileContent(filename string, text string, mode string) er directory := path.Dir(filename) - err := driver.Exec(c.App.Name, []string{"mkdir", "-p", directory}, "", []string{}) + _, err := driver.Exec(c.App.Name, []string{"mkdir", "-p", directory}, "", []string{}, false) if err != nil { return err } - err = driver.Exec(c.App.Name, []string{"tee", filename}, text, []string{}) + _, err = driver.Exec(c.App.Name, []string{"tee", filename}, text, []string{}, false) if err != nil { return err } - err = driver.Exec(c.App.Name, []string{"chown", directory, "app:app"}, "", []string{}) + _, err = driver.Exec(c.App.Name, []string{"chown", directory, "app:app"}, "", []string{}, false) if err != nil { return err } - err = driver.Exec(c.App.Name, []string{"chown", filename, "app:app"}, "", []string{}) + _, err = driver.Exec(c.App.Name, []string{"chown", filename, "app:app"}, "", []string{}, false) if err != nil { return err } - err = driver.Exec(c.App.Name, []string{"chmod", mode, filename}, "", []string{}) + _, err = driver.Exec(c.App.Name, []string{"chmod", mode, filename}, "", []string{}, false) return err } @@ -220,6 +227,31 @@ func (c *Container) SetFileContent(filename string, text string, mode string) er func (c *Container) SetTechnology(tech string) error { driver := c.getDriver() - err := driver.Exec(c.App.Name, []string{"su", "app", "-c", "rosti " + tech}, "", []string{}) + _, err := driver.Exec(c.App.Name, []string{"su", "app", "-c", "rosti " + tech}, "", []string{}, false) return err } + +// GetProcessList returns list of processes managed by supervisor. +func (c *Container) GetProcessList() (*[]Process, error) { + driver := c.getDriver() + + processes := []Process{} + + stdouterr, err := driver.Exec(c.App.Name, []string{"supervisorctl", "status"}, "", []string{}, true) + if err != nil { + return &processes, nil + } + + trimmed := strings.TrimSpace(string(*stdouterr)) + for _, row := range strings.Split(trimmed, "\n") { + fields := strings.Fields(row) + if len(fields) > 2 { + processes = append(processes, Process{ + Name: fields[0], + State: fields[1], + }) + } + } + + return &processes, nil +} diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..ad527ff --- /dev/null +++ b/handlers.go @@ -0,0 +1,449 @@ +package main + +import ( + "io/ioutil" + "net/http" + + "github.com/labstack/echo" + "github.com/rosti-cz/node-api/apps" + "github.com/rosti-cz/node-api/docker" + "github.com/rosti-cz/node-api/node" +) + +func homeHandler(c echo.Context) error { + return c.Render(http.StatusOK, "index.html", templateData{ + Token: configuredToken, + }) +} + +func listAppsHandler(c echo.Context) error { + err := gatherStates() + if err != nil { + return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + } + + applications, err := apps.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) + } + + app, err := apps.Get(name) + if err != nil { + return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + } + + app, err = apps.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) + } + + err = apps.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) + } + + appPointer, err := apps.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") + + app, err := apps.Get(name) + if err != nil { + return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + } + + container := docker.Container{ + App: app, + } + + 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") + + app, err := apps.Get(name) + if err != nil { + return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + } + + container := docker.Container{ + App: app, + } + + 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") + + app, err := apps.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) + } + + app, err := apps.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) + } + + app, err := apps.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) + } + + app, err := apps.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.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") + + app, err := apps.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) + } + + err = apps.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) + } + + err = apps.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 +func deleteAppHandler(c echo.Context) error { + name := c.Param("name") + + app, err := apps.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) + } + if status != "no-container" { + err = container.Delete() + if err != nil { + return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + } + } + + err = apps.Delete(name) + if err != nil { + return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + } + + 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") + + app, err := apps.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) +} diff --git a/main.go b/main.go index d8458a0..5bafa4d 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,11 @@ package main import ( - "io/ioutil" "log" - "net/http" "time" "github.com/labstack/echo" - "github.com/rosti-cz/node-api/apps" "github.com/rosti-cz/node-api/common" - "github.com/rosti-cz/node-api/docker" "github.com/rosti-cz/node-api/node" ) @@ -52,444 +48,59 @@ func main() { /* e.Use(TokenMiddleware) */ + // UI + e.GET("/", homeHandler) + // Returns list of apps - e.GET("/", func(c echo.Context) error { - return c.Render(http.StatusOK, "index.html", templateData{ - Token: configuredToken, - }) - }) - e.GET("/v1/apps", func(c echo.Context) error { - err := gatherStates() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - applications, err := apps.List() - - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - return c.JSON(http.StatusOK, applications) - }) + e.GET("/v1/apps", listAppsHandler) // Returns one app - e.GET("/v1/apps/:name", func(c echo.Context) error { - name := c.Param("name") - - err := updateState(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - app, err := apps.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - app, err = apps.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - return c.JSON(http.StatusOK, app) - }) + e.GET("/v1/apps/:name", getAppHandler) // 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. - e.POST("/v1/apps", func(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) - } - - err = apps.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"}) - }) + e.POST("/v1/apps", createAppHandler) // Update existing app - e.PUT("/v1/apps/:name", func(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) - } - - appPointer, err := apps.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"}) - }) + e.PUT("/v1/apps/:name", updateAppHandler) // Stop existing app - e.PUT("/v1/apps/:name/stop", func(c echo.Context) error { - name := c.Param("name") - - app, err := apps.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - err = container.Stop() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - return c.JSON(http.StatusOK, Message{Message: "ok"}) - }) + e.PUT("/v1/apps/:name/stop", stopAppHandler) // Start existing app - e.PUT("/v1/apps/:name/start", func(c echo.Context) error { - name := c.Param("name") - - app, err := apps.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - err = container.Start() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - return c.JSON(http.StatusOK, Message{Message: "ok"}) - }) + e.PUT("/v1/apps/:name/start", startAppHandler) // Stop existing app - e.PUT("/v1/apps/:name/restart", func(c echo.Context) error { - name := c.Param("name") + e.PUT("/v1/apps/:name/restart", restartAppHandler) - app, err := apps.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"}) - }) - - // Stats for existing app - e.GET("/v1/apps/:name/stats", func(c echo.Context) error { - name := c.Param("name") - - app, err := apps.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - cpu, memory, err := container.ResourceUsage() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - log.Println("DEBUG", cpu, memory) - - return c.JSON(http.StatusOK, Message{Message: "ok"}) - }) + // Application processes + e.GET("/v1/apps/:name/processes", getAppProcessesHandler) // Set password for the app user in the container - e.PUT("/v1/apps/:name/password", func(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) - } - - app, err := apps.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"}) - }) + e.PUT("/v1/apps/:name/password", setPasswordHandler) // Copies body of the request into /srv/.ssh/authorized_keys - e.PUT("/v1/apps/:name/keys", func(c echo.Context) error { - name := c.Param("name") + e.PUT("/v1/apps/:name/keys", setKeysHandler) - body, err := ioutil.ReadAll(c.Request().Body) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - app, err := apps.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"}) - }) - - e.PUT("/v1/apps/:name/set-services", func(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) - } - - app, err := apps.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.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"}) - }) + e.PUT("/v1/apps/:name/set-services", setServicesHandler) // Rebuilds existing app, it keeps the data but created the container again - e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error { - name := c.Param("name") - - app, err := apps.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"}) - }) + e.PUT("/v1/apps/:name/rebuild", rebuildAppHandler) // Adds new label - e.POST("/v1/apps/:name/labels", func(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) - } - - err = apps.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"}) - }) + e.POST("/v1/apps/:name/labels", addLabelHandler) // Removes existing label - e.DELETE("/v1/apps/:name/labels", func(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) - } - - err = apps.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"}) - }) + e.DELETE("/v1/apps/:name/labels", deleteLabelHandler) // Delete one app - e.DELETE("/v1/apps/:name", func(c echo.Context) error { - name := c.Param("name") - - app, err := apps.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) - } - if status != "no-container" { - err = container.Delete() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - err = apps.Delete(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - return c.JSON(http.StatusOK, Message{Message: "deleted"}) - }) + e.DELETE("/v1/apps/:name", deleteAppHandler) // Orphans returns directories in /srv that doesn't match any hosted application - e.GET("/v1/orphans", func(c echo.Context) error { - return c.JSON(http.StatusOK, []string{}) - }) + e.GET("/v1/orphans", getOrphansHander) // Return info about the node including performance index - e.GET("/v1/node", func(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) - }) + e.GET("/v1/node", getNodeInfoHandler) e.Logger.Fatal(e.Start("127.0.0.1:1323")) }