From e3a911cd05016f44ca6cd436ce2106f6b39881ad Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 2 May 2021 19:27:30 +0200 Subject: [PATCH] Can define volumes to collect artifacts --- .gitignore | 1 + app.go | 48 +++++++++++++++++++++++----- engine/docker/docker.go | 10 ++++-- engine/docker/volumes.go | 68 ++++++++++++++++++++++++++++++++++++++++ ui/index.html | 8 +++++ ui/js/minifass.js | 24 ++++++++++++++ 6 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 engine/docker/volumes.go diff --git a/.gitignore b/.gitignore index 0d5de8b..7e0998a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +artifacts minifaas vendor \ No newline at end of file diff --git a/app.go b/app.go index a89270e..b84b1ba 100644 --- a/app.go +++ b/app.go @@ -5,8 +5,10 @@ import ( "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" @@ -21,12 +23,20 @@ type App struct { } func NewApp() App { + //Check rights on artifacts directory + os.MkdirAll("./artifacts", os.ModePerm) + //gin.SetMode(gin.ReleaseMode) gin.ForceConsoleColor() router := gin.Default() ui.DeclareRoutes(router) + artifacts := http.Dir("./artifacts") + router.GET("/artifacts/*path", func(c *gin.Context) { + c.FileFromFS(c.Param("path"), artifacts) + }) + router.GET("/api/version", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"version": 0.1}) }) @@ -34,7 +44,7 @@ func NewApp() App { router.GET("/api/ps", func(c *gin.Context) { containers, err := docker.Ps() if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err}) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -42,25 +52,47 @@ func NewApp() App { }) router.GET("/api/run", func(c *gin.Context) { - ctrid, err := docker.Run("alpine", []string{"sh", "-c", "for i in `seq 20`; do echo $i; sleep 0.5; done"}) + myVolume, err := docker.CreateVolumeDir("/data", false) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err}) + 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") - stream, err := docker.Logs(c.Param("jobid")) - if err != nil { - log.Fatal("Unable to get log stream:", err) - } - r, w := io.Pipe() ctx, cancel := context.WithTimeout(c.Request.Context(), time.Second*10) diff --git a/engine/docker/docker.go b/engine/docker/docker.go index d28626e..fb7db08 100644 --- a/engine/docker/docker.go +++ b/engine/docker/docker.go @@ -6,6 +6,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" oci "github.com/opencontainers/image-spec/specs-go/v1" @@ -29,7 +30,7 @@ func Ps() ([]types.Container, error) { return containers, nil } -func Run(image string, cmd []string) (string, error) { +func Run(image string, cmd []string, mounts []mount.Mount) (string, error) { cli, err := newCli() if err != nil { return "", err @@ -42,8 +43,13 @@ func Run(image string, cmd []string) (string, error) { AttachStderr: true, Image: image, Cmd: cmd, + Volumes: map[string]struct{}{ + "/data": {}, + }, + }, + &container.HostConfig{ + Mounts: mounts, }, - &container.HostConfig{}, &network.NetworkingConfig{}, &oci.Platform{ Architecture: "amd64", diff --git a/engine/docker/volumes.go b/engine/docker/volumes.go new file mode 100644 index 0000000..2aa9fff --- /dev/null +++ b/engine/docker/volumes.go @@ -0,0 +1,68 @@ +package docker + +import ( + "context" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/mount" +) + +func CreateVolumeDir(target string, readOnly bool) (*mount.Mount, error) { + abs, err := filepath.Abs("artifacts") + if err != nil { + return nil, err + } + + dir, err := ioutil.TempDir(abs, "") + if err != nil { + return nil, err + } + + return &mount.Mount{ + Type: mount.TypeBind, + Source: dir, + Target: target, + ReadOnly: readOnly, + }, nil +} + +func GetArtifactsVolumes(id string) (ret []string, err error) { + abs, err := filepath.Abs("artifacts") + if err != nil { + return nil, err + } + + mnt, err := GetVolumes(id) + if err != nil { + return nil, err + } + + for _, m := range mnt { + if m.Type == mount.TypeBind && strings.HasPrefix(m.Source, abs) { + ret = append(ret, strings.TrimPrefix(m.Source, abs)) + } + } + + return +} + +func GetVolumes(id string) (ret []types.MountPoint, err error) { + cli, err := newCli() + if err != nil { + return nil, err + } + + ctr, err := cli.ContainerInspect(context.Background(), id) + if err != nil { + return nil, err + } + + for _, mnt := range ctr.Mounts { + ret = append(ret, mnt) + } + + return +} diff --git a/ui/index.html b/ui/index.html index edad158..106ad6a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -23,6 +23,14 @@ it's ID is + +