From 1634200ef7317f05864f6e14521a3527efbf5827 Mon Sep 17 00:00:00 2001 From: "Jonathan Leibiusky @xetorthio" Date: Tue, 16 May 2017 17:37:34 -0300 Subject: [PATCH] UI modifications for stack creation --- handlers/home.go | 26 ++---------------- handlers/new_session.go | 4 +-- services/docker.go | 24 +++++++++++++++++ services/session.go | 60 ++++++++++++++++++++++++++++++++++++++++- www/assets/app.js | 50 +++++++++++++++++++++++++++++++++- www/index.html | 33 +++++++++++++++++++++++ 6 files changed, 169 insertions(+), 28 deletions(-) diff --git a/handlers/home.go b/handlers/home.go index b3bd008..6731098 100644 --- a/handlers/home.go +++ b/handlers/home.go @@ -1,9 +1,7 @@ package handlers import ( - "log" "net/http" - "path" "github.com/gorilla/mux" "github.com/play-with-docker/play-with-docker/services" @@ -13,29 +11,9 @@ func Home(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) sessionId := vars["sessionId"] - stack := r.URL.Query().Get("stack") s := services.GetSession(sessionId) - if stack != "" { - go deployStack(s, stack) + if s.Stack != "" { + go s.DeployStack() } http.ServeFile(w, r, "./www/index.html") } - -func deployStack(s *services.Session, stack string) { - i, err := services.NewInstance(s, services.InstanceConfig{}) - if err != nil { - log.Printf("Error creating instance for stack [%s]: %s\n", stack, err) - } - err = i.UploadFromURL("https://raw.githubusercontent.com/play-with-docker/stacks/master" + stack) - if err != nil { - log.Printf("Error uploading stack file [%s]: %s\n", stack, err) - } - - fileName := path.Base(stack) - code, err := services.Exec(i.Name, []string{"docker-compose", "-f", "/var/run/pwd/uploads/" + fileName, "up", "-d"}) - if err != nil { - log.Printf("Error executing stack [%s]: %s\n", stack, err) - } - - log.Printf("Stack execution finished with code %d\n", code) -} diff --git a/handlers/new_session.go b/handlers/new_session.go index cf79084..fc344de 100644 --- a/handlers/new_session.go +++ b/handlers/new_session.go @@ -39,7 +39,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) { } duration := services.GetDuration(reqDur) - s, err := services.NewSession(duration) + s, err := services.NewSession(duration, stack) if err != nil { log.Println(err) //TODO: Return some error code @@ -54,7 +54,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) { } if stack != "" { - http.Redirect(rw, req, fmt.Sprintf("http://%s/p/%s?stack=%s", hostname, s.Id, stack), http.StatusFound) + http.Redirect(rw, req, fmt.Sprintf("http://%s/p/%s", hostname, s.Id), http.StatusFound) return } http.Redirect(rw, req, fmt.Sprintf("http://%s/p/%s", hostname, s.Id), http.StatusFound) diff --git a/services/docker.go b/services/docker.go index 02af4be..bed7ece 100644 --- a/services/docker.go +++ b/services/docker.go @@ -365,3 +365,27 @@ func Exec(instanceName string, command []string) (int, error) { return ins.ExitCode, nil } +func ExecAttach(instanceName string, command []string, out io.Writer) (int, error) { + e, err := c.ContainerExecCreate(context.Background(), instanceName, types.ExecConfig{Cmd: command, AttachStdout: true, AttachStderr: true, Tty: true}) + if err != nil { + return 0, err + } + resp, err := c.ContainerExecAttach(context.Background(), e.ID, types.ExecConfig{AttachStdout: true, AttachStderr: true, Tty: true}) + if err != nil { + return 0, err + } + io.Copy(out, resp.Reader) + var ins types.ContainerExecInspect + for _ = range time.Tick(1 * time.Second) { + ins, err = c.ContainerExecInspect(context.Background(), e.ID) + if ins.Running { + continue + } + if err != nil { + return 0, err + } + break + } + return ins.ExitCode, nil + +} diff --git a/services/session.go b/services/session.go index 61babac..7ea360a 100644 --- a/services/session.go +++ b/services/session.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "path" "sort" "strings" "sync" @@ -56,6 +57,17 @@ type Session struct { scheduled bool `json:"-"` ticker *time.Ticker `json:"-"` PwdIpAddress string `json:"pwd_ip_address"` + Ready bool `json:"ready"` + Stack string `json:"stack"` +} + +type sessionBuilderWriter struct { + session *Session +} + +func (s *sessionBuilderWriter) Write(p []byte) (n int, err error) { + wsServer.BroadcastTo(s.session.Id, "session builder out", string(p)) + return len(p), nil } func (s *Session) Lock() { @@ -78,6 +90,48 @@ func (s *Session) GetSmallestViewPort() ViewPort { return ViewPort{Rows: minRows, Cols: minCols} } +func (s *Session) DeployStack() error { + s.Lock() + defer s.Unlock() + + if s.Ready { + // a stack was already deployed on this session, just ignore + return nil + } + + s.setReady(false) + i, err := NewInstance(s, InstanceConfig{}) + if err != nil { + log.Printf("Error creating instance for stack [%s]: %s\n", s.Stack, err) + return err + } + err = i.UploadFromURL("https://raw.githubusercontent.com/play-with-docker/stacks/master" + s.Stack) + if err != nil { + log.Printf("Error uploading stack file [%s]: %s\n", s.Stack, err) + return err + } + + w := sessionBuilderWriter{session: s} + fileName := path.Base(s.Stack) + code, err := ExecAttach(i.Name, []string{"docker-compose", "-f", "/var/run/pwd/uploads/" + fileName, "up", "-d"}, &w) + if err != nil { + log.Printf("Error executing stack [%s]: %s\n", s.Stack, err) + return err + } + + log.Printf("Stack execution finished with code %d\n", code) + s.setReady(true) + if err := saveSessionsToDisk(); err != nil { + return err + } + return nil +} + +func (s *Session) setReady(ready bool) { + s.Ready = ready + wsServer.BroadcastTo(s.Id, "session ready", s.Ready) +} + func (s *Session) AddNewClient(c *Client) { s.clients = append(s.clients, c) setGauges() @@ -243,12 +297,16 @@ func GetDuration(reqDur string) time.Duration { return defaultDuration } -func NewSession(duration time.Duration) (*Session, error) { +func NewSession(duration time.Duration, stack string) (*Session, error) { s := &Session{} s.Id = uuid.NewV4().String() s.Instances = map[string]*Instance{} s.CreatedAt = time.Now() s.ExpiresAt = s.CreatedAt.Add(duration) + if stack == "" { + s.Ready = true + } + s.Stack = stack log.Printf("NewSession id=[%s]\n", s.Id) // Schedule cleanup of the session diff --git a/www/assets/app.js b/www/assets/app.js index b07930f..e1fb396 100644 --- a/www/assets/app.js +++ b/www/assets/app.js @@ -11,6 +11,14 @@ }, 500); }]); + function SessionBuilderModalController($mdDialog, $scope) { + $scope.createBuilderTerminal(); + + $scope.closeSessionBuilder = function() { + $mdDialog.cancel(); + } + } + app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', function($scope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService) { $scope.sessionId = window.location.pathname.replace('/p/', ''); $scope.instances = []; @@ -23,7 +31,7 @@ $scope.newInstanceBtnText = '+ Add new instance'; $scope.deleteInstanceBtnText = 'Delete'; $scope.isInstanceBeingDeleted = false; - + var selectedKeyboardShortcuts = KeyboardShortcutService.getCurrentShortcuts(); angular.element($window).bind('resize', function() { @@ -90,11 +98,28 @@ }); } + $scope.setSessionState = function(state) { + $scope.ready = state; + + if (!state) { + $mdDialog.show({ + controller: SessionBuilderModalController, + templateUrl: "session-builder-modal.html", + parent: angular.element(document.body), + clickOutsideToClose: false, + scope: $scope, + preserveScope: true + }); + } + } + $scope.getSession = function(sessionId) { $http({ method: 'GET', url: '/sessions/' + $scope.sessionId, }).then(function(response) { + $scope.setSessionState(response.data.ready); + if (response.data.created_at) { $scope.expiresAt = moment(response.data.expires_at); setInterval(function() { @@ -104,6 +129,14 @@ } var socket = io({ path: '/sessions/' + sessionId + '/ws' }); + socket.on('session ready', function(ready) { + $scope.setSessionState(ready); + }); + + socket.on('session builder out', function(data) { + $scope.builderTerminal.write(data); + }); + socket.on('terminal out', function(name, data) { var instance = $scope.idx[name]; @@ -236,6 +269,21 @@ $scope.getSession($scope.sessionId); + $scope.createBuilderTerminal = function() { + var builderTerminalContainer = document.getElementById('builder-terminal'); + // For some reason the dialog DOM might not be ready, so we just keep trying + if (!builderTerminalContainer) { + setTimeout($scope.createBuilderTerminal, 100); + return; + } + var term = new Terminal({ + cursorBlink: false + }); + + term.open(builderTerminalContainer); + term.resize(80, 24); + $scope.builderTerminal = term; + } function createTerminal(instance, cb) { if (instance.term) { return instance.term; diff --git a/www/index.html b/www/index.html index 089e506..3fc2ae3 100644 --- a/www/index.html +++ b/www/index.html @@ -113,6 +113,39 @@ + +