Rest of the endpoints finished
This commit is contained in:
parent
0181236e23
commit
4f9dac7f5c
5 changed files with 208 additions and 39 deletions
32
api.http
32
api.http
|
@ -3,8 +3,8 @@ Content-type: application/json
|
|||
|
||||
{
|
||||
"name": "test_1234",
|
||||
"ssh_port": 10000,
|
||||
"http_port": 10001,
|
||||
"ssh_port": 46500,
|
||||
"http_port": 46501,
|
||||
"image": "docker.io/rosti/runtime:2020.04-1",
|
||||
"cpu": 100,
|
||||
"memory": 128000
|
||||
|
@ -16,8 +16,8 @@ PUT http://localhost:1323/v1/apps/test_1234
|
|||
Content-type: application/json
|
||||
|
||||
{
|
||||
"ssh_port": 36500,
|
||||
"http_port": 36501
|
||||
"ssh_port": 46500,
|
||||
"http_port": 46501
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,3 +25,27 @@ Content-type: application/json
|
|||
|
||||
PUT http://localhost:1323/v1/apps/test_1234/start
|
||||
Content-type: application/json
|
||||
|
||||
###
|
||||
|
||||
|
||||
PUT http://localhost:1323/v1/apps/test_1234/stop
|
||||
Content-type: application/json
|
||||
|
||||
###
|
||||
|
||||
DELETE http://localhost:1323/v1/apps/test_1234
|
||||
Content-type: application/json
|
||||
|
||||
###
|
||||
|
||||
|
||||
GET http://localhost:1323/v1/apps/test_1234/stats
|
||||
Content-type: application/json
|
||||
|
||||
|
||||
###
|
||||
|
||||
|
||||
GET http://localhost:1323/v1/apps
|
||||
Content-type: application/json
|
||||
|
|
|
@ -23,6 +23,13 @@ type Label struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// AppState contains info about runnint application, it's not saved in the database
|
||||
type AppState struct {
|
||||
State string `json:"state"`
|
||||
CPUUsage int `json:"cpu_usage"`
|
||||
MemoryUsage int `json:"memory_usage"`
|
||||
}
|
||||
|
||||
// App keeps info about hosted application
|
||||
type App struct {
|
||||
gorm.Model
|
||||
|
@ -34,6 +41,8 @@ type App struct {
|
|||
CPU int `json:"cpu"` // percentage, 200 means two CPU
|
||||
Memory int `json:"memory"` // Limit in MB
|
||||
// Labels []Label `json:"labels"` // username:cx or user_id:1
|
||||
|
||||
State AppState `json:"state" gorm:"-"`
|
||||
}
|
||||
|
||||
// Validate do basic checks of the struct values
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -25,7 +24,7 @@ const dockerTimeout = 10
|
|||
const dockerSock = "unix:///var/run/docker.sock"
|
||||
|
||||
// DOCKER_API_VERSION set API version of Docker, 1.40 belongs to Docker 19.03.11
|
||||
const dockerAPIVersion = "1.40"
|
||||
const dockerAPIVersion = "1.24"
|
||||
|
||||
// Driver keeps everything for connection to Docker
|
||||
type Driver struct{}
|
||||
|
@ -76,8 +75,10 @@ func (d *Driver) Status(name string) (string, error) {
|
|||
}
|
||||
|
||||
containerID, err := d.nameToID(name)
|
||||
if err != nil && err.Error() == "no container found" {
|
||||
return "no-container", err
|
||||
}
|
||||
if err != nil {
|
||||
status = "no-container"
|
||||
return status, err
|
||||
}
|
||||
|
||||
|
@ -117,7 +118,9 @@ func (d *Driver) Stats(name string) (float64, int, error) {
|
|||
if err != nil {
|
||||
return 0.0, 0, err
|
||||
}
|
||||
log.Println(data)
|
||||
// It returns one JSON:
|
||||
// {"read":"2020-07-11T20:42:31.486726241Z","preread":"2020-07-11T20:42:30.484048602Z","pids_stats":{"current":7},"blkio_stats":{"io_service_bytes_recursive":[{"major":253,"minor":0,"op":"Read","value":0},{"major":253,"minor":0,"op":"Write","value":20480},{"major":253,"minor":0,"op":"Sync","value":12288},{"major":253,"minor":0,"op":"Async","value":8192},{"major":253,"minor":0,"op":"Discard","value":0},{"major":253,"minor":0,"op":"Total","value":20480}],"io_serviced_recursive":[{"major":253,"minor":0,"op":"Read","value":0},{"major":253,"minor":0,"op":"Write","value":5},{"major":253,"minor":0,"op":"Sync","value":3},{"major":253,"minor":0,"op":"Async","value":2},{"major":253,"minor":0,"op":"Discard","value":0},{"major":253,"minor":0,"op":"Total","value":5}],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]},"num_procs":0,"storage_stats":{},"cpu_stats":{"cpu_usage":{"total_usage":758392753,"percpu_usage":[302688474,0,11507116,124238500,222136766,5656446,3009320,0,19406386,1397028,6201423,62151294,0,0,0,0],"usage_in_kernelmode":100000000,"usage_in_usermode":640000000},"system_cpu_usage":119385810000000,"online_cpus":12,"throttling_data":{"periods":21,"throttled_periods":1,"throttled_time":2995938}},"precpu_stats":{"cpu_usage":{"total_usage":758282347,"percpu_usage":[302688474,0,11507116,124238500,222026360,5656446,3009320,0,19406386,1397028,6201423,62151294,0,0,0,0],"usage_in_kernelmode":100000000,"usage_in_usermode":640000000},"system_cpu_usage":119373720000000,"online_cpus":12,"throttling_data":{"periods":21,"throttled_periods":1,"throttled_time":2995938}},"memory_stats":{"usage":21626880,"max_usage":22630400,"stats":{"active_anon":15949824,"active_file":0,"cache":0,"dirty":0,"hierarchical_memory_limit":144179200,"hierarchical_memsw_limit":288358400,"inactive_anon":0,"inactive_file":0,"mapped_file":0,"pgfault":13167,"pgmajfault":0,"pgpgin":7293,"pgpgout":3406,"rss":15900672,"rss_huge":0,"total_active_anon":15949824,"total_active_file":0,"total_cache":0,"total_dirty":0,"total_inactive_anon":0,"total_inactive_file":0,"total_mapped_file":0,"total_pgfault":13167,"total_pgmajfault":0,"total_pgpgin":7293,"total_pgpgout":3406,"total_rss":15900672,"total_rss_huge":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0},"limit":144179200},"name":"/test_1234","id":"576878d645efecc8e5e2a57b88351f7b5c551e3fc72dc8473fd965d10dfddbec","networks":{"eth0":{"rx_bytes":6150,"rx_packets":37,"rx_errors":0,"rx_dropped":0,"tx_bytes":0,"tx_packets":0,"tx_errors":0,"tx_dropped":0}}}
|
||||
log.Println(string(data))
|
||||
|
||||
return 0.0, 0, nil
|
||||
}
|
||||
|
@ -248,24 +251,26 @@ func (d *Driver) Create(name string, image string, volumePath string, HTTPPort i
|
|||
return "", err
|
||||
}
|
||||
|
||||
portmaps := make(nat.PortMap, 1)
|
||||
portmaps := nat.PortMap{}
|
||||
|
||||
portbindingsHTTP := make([]nat.PortBinding, 1)
|
||||
portbindingsHTTP[0] = nat.PortBinding{
|
||||
HostPort: strconv.Itoa(HTTPPort) + "/tcp",
|
||||
}
|
||||
portmaps["8000/tcp"] = portbindingsHTTP
|
||||
// portbindingsHTTP := make([]nat.PortBinding, 1)
|
||||
// portbindingsHTTP[0] = nat.PortBinding{
|
||||
// HostIP: "0.0.0.0",
|
||||
// HostPort: strconv.Itoa(HTTPPort) + "/tcp",
|
||||
// }
|
||||
// portmaps["8000/tcp"] = portbindingsHTTP
|
||||
|
||||
if SSHPort != 0 {
|
||||
portbindingsSSH := make([]nat.PortBinding, 1)
|
||||
portbindingsSSH[0] = nat.PortBinding{
|
||||
HostPort: strconv.Itoa(SSHPort) + "/tcp",
|
||||
}
|
||||
portmaps["22/tcp"] = portbindingsSSH
|
||||
}
|
||||
// if SSHPort != 0 {
|
||||
// portbindingsSSH := make([]nat.PortBinding, 1)
|
||||
// portbindingsSSH[0] = nat.PortBinding{
|
||||
// HostIP: "0.0.0.0",
|
||||
// HostPort: strconv.Itoa(SSHPort) + "/tcp",
|
||||
// }
|
||||
// portmaps["22/tcp"] = portbindingsSSH
|
||||
// }
|
||||
|
||||
createdContainer, err := cli.ContainerCreate(
|
||||
context.TODO(),
|
||||
context.Background(),
|
||||
&container.Config{
|
||||
Hostname: name,
|
||||
Env: []string{},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path"
|
||||
|
||||
"github.com/rosti-cz/apps-api/apps"
|
||||
|
@ -8,7 +9,7 @@ import (
|
|||
|
||||
// Container extends App struct from App
|
||||
type Container struct {
|
||||
App apps.App `json:"app"`
|
||||
App *apps.App `json:"app"`
|
||||
}
|
||||
|
||||
func (c *Container) getDriver() *Driver {
|
||||
|
@ -21,8 +22,26 @@ func (c *Container) volumeHostPath() string {
|
|||
return path.Join("/srv", c.App.Name)
|
||||
}
|
||||
|
||||
// GetApp app object with populated state fields
|
||||
func (c *Container) GetApp() (*apps.App, error) {
|
||||
status, err := c.Status()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
state := apps.AppState{
|
||||
State: status,
|
||||
CPUUsage: 0,
|
||||
MemoryUsage: 0,
|
||||
}
|
||||
|
||||
c.App.State = state
|
||||
|
||||
return c.App, nil
|
||||
}
|
||||
|
||||
// Status returns state of the container
|
||||
// Possible values: running, stopped, data-only, unknown
|
||||
// Possible values: running, stopped, no-container, unknown
|
||||
func (c *Container) Status() (string, error) {
|
||||
status := "unknown"
|
||||
|
||||
|
@ -32,15 +51,14 @@ func (c *Container) Status() (string, error) {
|
|||
|
||||
driver := c.getDriver()
|
||||
containerStatus, err := driver.Status(c.App.Name)
|
||||
if err != nil && err.Error() == "no container found" {
|
||||
return "no-container", nil
|
||||
}
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
||||
if containerStatus == "no-container" {
|
||||
status = "data-only"
|
||||
} else {
|
||||
status = containerStatus
|
||||
}
|
||||
status = containerStatus
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
@ -121,6 +139,9 @@ func (c *Container) Delete() error {
|
|||
|
||||
volumePath := path.Join("/srv", c.App.Name)
|
||||
err = removeDirectory(volumePath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
|
128
main.go
128
main.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
|
@ -25,13 +26,25 @@ func main() {
|
|||
|
||||
// Returns list of apps
|
||||
e.GET("/v1/apps", func(c echo.Context) error {
|
||||
apps, err := apps.List()
|
||||
applications, err := apps.List()
|
||||
|
||||
if err != nil {
|
||||
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, apps)
|
||||
populatedApps := []*apps.App{}
|
||||
for _, app := range *applications {
|
||||
container := docker.Container{
|
||||
App: &app,
|
||||
}
|
||||
populatedApp, err := container.GetApp()
|
||||
if err != nil {
|
||||
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||
}
|
||||
populatedApps = append(populatedApps, populatedApp)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, populatedApps)
|
||||
})
|
||||
|
||||
// Returns one app
|
||||
|
@ -43,6 +56,14 @@ func main() {
|
|||
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||
}
|
||||
|
||||
container := docker.Container{
|
||||
App: app,
|
||||
}
|
||||
_, err = container.GetApp()
|
||||
if err != nil {
|
||||
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, app)
|
||||
})
|
||||
|
||||
|
@ -63,7 +84,7 @@ func main() {
|
|||
}
|
||||
|
||||
container := docker.Container{
|
||||
App: app,
|
||||
App: &app,
|
||||
}
|
||||
|
||||
err = container.Create()
|
||||
|
@ -100,7 +121,7 @@ func main() {
|
|||
app = *appPointer
|
||||
|
||||
container := docker.Container{
|
||||
App: app,
|
||||
App: &app,
|
||||
}
|
||||
|
||||
err = container.Destroy()
|
||||
|
@ -123,7 +144,23 @@ func main() {
|
|||
|
||||
// Stop existing app
|
||||
e.PUT("/v1/apps/:name/stop", func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, map[string]string{})
|
||||
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
|
||||
|
@ -136,7 +173,7 @@ func main() {
|
|||
}
|
||||
|
||||
container := docker.Container{
|
||||
App: *app,
|
||||
App: app,
|
||||
}
|
||||
|
||||
err = container.Start()
|
||||
|
@ -149,19 +186,92 @@ func main() {
|
|||
|
||||
// Stop existing app
|
||||
e.PUT("/v1/apps/:name/restart", func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, map[string]string{})
|
||||
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"})
|
||||
})
|
||||
|
||||
// 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"})
|
||||
})
|
||||
|
||||
// Rebuilds existing app, it keeps the data but created the container again
|
||||
e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, map[string]string{})
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, Message{Message: "ok"})
|
||||
})
|
||||
|
||||
// Delete one app
|
||||
e.DELETE("/v1/apps/:name", func(c echo.Context) error {
|
||||
name := c.Param("name")
|
||||
|
||||
err := apps.Delete(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.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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue