144 lines
3.1 KiB
Go
144 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types/mount"
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
"github.com/gin-gonic/gin"
|
|
"nhooyr.io/websocket"
|
|
|
|
"github.com/nemunaire/minifaas/config"
|
|
"github.com/nemunaire/minifaas/engine/docker"
|
|
"github.com/nemunaire/minifaas/ui"
|
|
)
|
|
|
|
type App struct {
|
|
router *gin.Engine
|
|
srv *http.Server
|
|
cfg *config.Config
|
|
}
|
|
|
|
func NewApp(cfg *config.Config) App {
|
|
//Check rights on artifacts directory
|
|
os.MkdirAll("./artifacts", os.ModePerm)
|
|
|
|
if !cfg.Dev {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
}
|
|
gin.ForceConsoleColor()
|
|
router := gin.Default()
|
|
|
|
ui.DeclareRoutes(router, cfg)
|
|
|
|
artifacts := http.Dir("./artifacts")
|
|
router.GET("/artifacts/*path", func(c *gin.Context) {
|
|
if c.Param("path") == "/" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
|
|
} else {
|
|
c.FileFromFS(c.Param("path"), artifacts)
|
|
}
|
|
})
|
|
|
|
router.GET("/api/version", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"version": 0.1})
|
|
})
|
|
|
|
router.GET("/api/run", func(c *gin.Context) {
|
|
myVolume, err := docker.CreateVolumeDir("/data", false)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctrid, err := docker.Run(
|
|
"alpine",
|
|
[]string{"sh", "-c", "touch /data/work_done; for i in `seq 20`; do echo $i; sleep 0.5; done"},
|
|
[]mount.Mount{*myVolume},
|
|
)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"jobid": ctrid})
|
|
})
|
|
|
|
router.GET("/api/jobs/:jobid/volumes", func(c *gin.Context) {
|
|
volumes, err := docker.GetArtifactsVolumes(c.Param("jobid"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, volumes)
|
|
})
|
|
|
|
router.GET("/api/jobs/:jobid/logs", func(c *gin.Context) {
|
|
stream, err := docker.Logs(c.Param("jobid"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ws, err := websocket.Accept(c.Writer, c.Request, nil)
|
|
if err != nil {
|
|
log.Fatal("error get connection", err)
|
|
}
|
|
defer ws.Close(websocket.StatusInternalError, "the sky is falling")
|
|
|
|
r, w := io.Pipe()
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), time.Second*10)
|
|
defer cancel()
|
|
|
|
go func(reader io.Reader) {
|
|
buf := make([]byte, 128)
|
|
for {
|
|
n, err := reader.Read(buf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
ws.Write(ctx, websocket.MessageText, buf[:n])
|
|
}
|
|
}(r)
|
|
|
|
_, err = stdcopy.StdCopy(w, w, stream)
|
|
if err != nil && err != io.EOF {
|
|
log.Fatal("Something goes wrong during stream:", err)
|
|
}
|
|
|
|
ws.Close(websocket.StatusNormalClosure, "")
|
|
})
|
|
|
|
app := App{
|
|
router: router,
|
|
cfg: cfg,
|
|
}
|
|
|
|
return app
|
|
}
|
|
|
|
func (app *App) Start() {
|
|
app.srv = &http.Server{
|
|
Addr: app.cfg.Bind,
|
|
Handler: app.router,
|
|
}
|
|
|
|
if err := app.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("listen: %s\n", err)
|
|
}
|
|
}
|
|
|
|
func (app *App) Stop() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := app.srv.Shutdown(ctx); err != nil {
|
|
log.Fatal("Server Shutdown:", err)
|
|
}
|
|
}
|