mirror of
https://github.com/bingohuang/docker-labs.git
synced 2025-07-14 18:27:25 +08:00
commit
6a0e07aeec
@ -5,4 +5,5 @@ RUN apk add --no-cache git tmux py-pip apache2-utils vim \
|
|||||||
|
|
||||||
COPY vimrc ./root/.vimrc
|
COPY vimrc ./root/.vimrc
|
||||||
|
|
||||||
|
CMD docker daemon --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2375 --storage-driver=vfs &>- & /bin/sh
|
||||||
|
|
||||||
|
7
api.go
7
api.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/franela/play-with-docker/handlers"
|
"github.com/franela/play-with-docker/handlers"
|
||||||
"github.com/franela/play-with-docker/services"
|
"github.com/franela/play-with-docker/services"
|
||||||
@ -11,11 +12,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
server := services.CreateWSServer()
|
server := services.CreateWSServer()
|
||||||
|
|
||||||
server.On("connection", handlers.WS)
|
server.On("connection", handlers.WS)
|
||||||
server.On("error", handlers.WSError)
|
server.On("error", handlers.WSError)
|
||||||
|
|
||||||
|
err := services.LoadSessionsFromDisk()
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
log.Fatal("Error decoding sessions from disk ", err)
|
||||||
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.StrictSlash(false)
|
r.StrictSlash(false)
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ type ViewPort struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
SO socketio.Socket
|
Id string
|
||||||
|
so socketio.Socket
|
||||||
ViewPort ViewPort
|
ViewPort ViewPort
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ func (c *Client) ResizeViewPort(cols, rows uint) {
|
|||||||
func NewClient(so socketio.Socket, session *Session) *Client {
|
func NewClient(so socketio.Socket, session *Session) *Client {
|
||||||
so.Join(session.Id)
|
so.Join(session.Id)
|
||||||
|
|
||||||
c := &Client{SO: so}
|
c := &Client{so: so, Id: so.Id()}
|
||||||
|
|
||||||
so.On("session close", func() {
|
so.On("session close", func() {
|
||||||
CloseSession(session)
|
CloseSession(session)
|
||||||
@ -33,8 +34,8 @@ func NewClient(so socketio.Socket, session *Session) *Client {
|
|||||||
so.On("terminal in", func(name, data string) {
|
so.On("terminal in", func(name, data string) {
|
||||||
// User wrote something on the terminal. Need to write it to the instance terminal
|
// User wrote something on the terminal. Need to write it to the instance terminal
|
||||||
instance := GetInstance(session, name)
|
instance := GetInstance(session, name)
|
||||||
if instance != nil && len(data) > 0 {
|
if instance != nil && instance.conn != nil && len(data) > 0 {
|
||||||
instance.Conn.Conn.Write([]byte(data))
|
instance.conn.Conn.Write([]byte(data))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -53,13 +54,13 @@ func NewClient(so socketio.Socket, session *Session) *Client {
|
|||||||
})
|
})
|
||||||
so.On("disconnection", func() {
|
so.On("disconnection", func() {
|
||||||
// Client has disconnected. Remove from session and recheck terminal sizes.
|
// Client has disconnected. Remove from session and recheck terminal sizes.
|
||||||
for i, cl := range session.Clients {
|
for i, cl := range session.clients {
|
||||||
if cl.SO.Id() == c.SO.Id() {
|
if cl.Id == c.Id {
|
||||||
session.Clients = append(session.Clients[:i], session.Clients[i+1:]...)
|
session.clients = append(session.clients[:i], session.clients[i+1:]...)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(session.Clients) > 0 {
|
if len(session.clients) > 0 {
|
||||||
vp := session.GetSmallestViewPort()
|
vp := session.GetSmallestViewPort()
|
||||||
// Resize all terminals in the session
|
// Resize all terminals in the session
|
||||||
wsServer.BroadcastTo(session.Id, "viewport resize", vp.Cols, vp.Rows)
|
wsServer.BroadcastTo(session.Id, "viewport resize", vp.Cols, vp.Rows)
|
||||||
|
@ -55,20 +55,10 @@ func DeleteNetwork(id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateExecConnection(id string, ctx context.Context) (string, error) {
|
func CreateAttachConnection(id string, ctx context.Context) (*types.HijackedResponse, error) {
|
||||||
conf := types.ExecConfig{Tty: true, AttachStdin: true, AttachStderr: true, AttachStdout: true, Cmd: []string{"sh"}, DetachKeys: "ctrl-x,ctrl-x"}
|
|
||||||
resp, err := c.ContainerExecCreate(ctx, id, conf)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AttachExecConnection(execId string, ctx context.Context) (*types.HijackedResponse, error) {
|
|
||||||
conf := types.ExecConfig{Tty: true, AttachStdin: true, AttachStderr: true, AttachStdout: true, DetachKeys: "ctrl-x,ctrl-x"}
|
|
||||||
conn, err := c.ContainerExecAttach(ctx, execId, conf)
|
|
||||||
|
|
||||||
|
conf := types.ContainerAttachOptions{true, true, true, true, "ctrl-x,ctrl-x", true}
|
||||||
|
conn, err := c.ContainerAttach(ctx, id, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -76,8 +66,8 @@ func AttachExecConnection(execId string, ctx context.Context) (*types.HijackedRe
|
|||||||
return &conn, nil
|
return &conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResizeExecConnection(execId string, ctx context.Context, cols, rows uint) error {
|
func ResizeConnection(name string, cols, rows uint) error {
|
||||||
return c.ContainerExecResize(ctx, execId, types.ResizeOptions{Height: rows, Width: cols})
|
return c.ContainerResize(context.Background(), name, types.ResizeOptions{Height: rows, Width: cols})
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateInstance(net string, dindImage string) (*Instance, error) {
|
func CreateInstance(net string, dindImage string) (*Instance, error) {
|
||||||
@ -88,7 +78,7 @@ func CreateInstance(net string, dindImage string) (*Instance, error) {
|
|||||||
t := true
|
t := true
|
||||||
h.Resources.OomKillDisable = &t
|
h.Resources.OomKillDisable = &t
|
||||||
|
|
||||||
conf := &container.Config{Image: dindImage, Tty: true}
|
conf := &container.Config{Image: dindImage, Tty: true, OpenStdin: true, AttachStdin: true, AttachStdout: true, AttachStderr: true}
|
||||||
container, err := c.ContainerCreate(context.Background(), conf, h, nil, "")
|
container, err := c.ContainerCreate(context.Background(), conf, h, nil, "")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -5,20 +5,31 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/text/encoding"
|
"golang.org/x/text/encoding"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var rw sync.Mutex
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
Session *Session `json:"-"`
|
session *Session `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Conn *types.HijackedResponse `json:"-"`
|
conn *types.HijackedResponse `json:"-"`
|
||||||
ExecId string `json:"-"`
|
ctx context.Context `json:"-"`
|
||||||
Ctx context.Context `json:"-"`
|
}
|
||||||
|
|
||||||
|
func (i *Instance) IsConnected() bool {
|
||||||
|
return i.conn != nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) SetSession(s *Session) {
|
||||||
|
i.session = s
|
||||||
}
|
}
|
||||||
|
|
||||||
var dindImage string
|
var dindImage string
|
||||||
@ -43,14 +54,18 @@ func NewInstance(session *Session) (*Instance, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
instance.Session = session
|
instance.session = session
|
||||||
|
|
||||||
if session.Instances == nil {
|
if session.Instances == nil {
|
||||||
session.Instances = make(map[string]*Instance)
|
session.Instances = make(map[string]*Instance)
|
||||||
}
|
}
|
||||||
session.Instances[instance.Name] = instance
|
session.Instances[instance.Name] = instance
|
||||||
|
|
||||||
go instance.Exec()
|
go instance.Attach()
|
||||||
|
|
||||||
|
rw.Lock()
|
||||||
|
err = saveSessionsToDisk()
|
||||||
|
rw.Unlock()
|
||||||
|
|
||||||
wsServer.BroadcastTo(session.Id, "new instance", instance.Name, instance.IP, instance.Hostname)
|
wsServer.BroadcastTo(session.Id, "new instance", instance.Name, instance.IP, instance.Hostname)
|
||||||
|
|
||||||
@ -62,28 +77,23 @@ type sessionWriter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *sessionWriter) Write(p []byte) (n int, err error) {
|
func (s *sessionWriter) Write(p []byte) (n int, err error) {
|
||||||
wsServer.BroadcastTo(s.instance.Session.Id, "terminal out", s.instance.Name, string(p))
|
wsServer.BroadcastTo(s.instance.session.Id, "terminal out", s.instance.Name, string(p))
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) ResizeTerminal(cols, rows uint) error {
|
func (i *Instance) ResizeTerminal(cols, rows uint) error {
|
||||||
return ResizeExecConnection(i.ExecId, i.Ctx, cols, rows)
|
return ResizeConnection(i.Name, cols, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) Exec() {
|
func (i *Instance) Attach() {
|
||||||
i.Ctx = context.Background()
|
i.ctx = context.Background()
|
||||||
|
conn, err := CreateAttachConnection(i.Name, i.ctx)
|
||||||
|
|
||||||
id, err := CreateExecConnection(i.Name, i.Ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
i.ExecId = id
|
|
||||||
conn, err := AttachExecConnection(id, i.Ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
i.Conn = conn
|
i.conn = conn
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
encoder := encoding.Replacement.NewEncoder()
|
encoder := encoding.Replacement.NewEncoder()
|
||||||
@ -92,10 +102,9 @@ func (i *Instance) Exec() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-i.Ctx.Done():
|
case <-i.ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetInstance(session *Session, name string) *Instance {
|
func GetInstance(session *Session, name string) *Instance {
|
||||||
//TODO: Use redis
|
//TODO: Use redis
|
||||||
return session.Instances[name]
|
return session.Instances[name]
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/gob"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,17 +15,25 @@ import (
|
|||||||
var wsServer *socketio.Server
|
var wsServer *socketio.Server
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
sync.Mutex
|
rw sync.Mutex
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Instances map[string]*Instance `json:"instances"`
|
Instances map[string]*Instance `json:"instances"`
|
||||||
Clients []*Client `json:"-"`
|
clients []*Client `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Lock() {
|
||||||
|
s.rw.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Unlock() {
|
||||||
|
s.rw.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) GetSmallestViewPort() ViewPort {
|
func (s *Session) GetSmallestViewPort() ViewPort {
|
||||||
minRows := s.Clients[0].ViewPort.Rows
|
minRows := s.clients[0].ViewPort.Rows
|
||||||
minCols := s.Clients[0].ViewPort.Cols
|
minCols := s.clients[0].ViewPort.Cols
|
||||||
|
|
||||||
for _, c := range s.Clients {
|
for _, c := range s.clients {
|
||||||
minRows = uint(math.Min(float64(minRows), float64(c.ViewPort.Rows)))
|
minRows = uint(math.Min(float64(minRows), float64(c.ViewPort.Rows)))
|
||||||
minCols = uint(math.Min(float64(minCols), float64(c.ViewPort.Cols)))
|
minCols = uint(math.Min(float64(minCols), float64(c.ViewPort.Cols)))
|
||||||
}
|
}
|
||||||
@ -32,7 +42,7 @@ func (s *Session) GetSmallestViewPort() ViewPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) AddNewClient(c *Client) {
|
func (s *Session) AddNewClient(c *Client) {
|
||||||
s.Clients = append(s.Clients, c)
|
s.clients = append(s.clients, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessions map[string]*Session
|
var sessions map[string]*Session
|
||||||
@ -51,12 +61,12 @@ func CreateWSServer() *socketio.Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CloseSession(s *Session) error {
|
func CloseSession(s *Session) error {
|
||||||
s.Lock()
|
s.rw.Lock()
|
||||||
defer s.Unlock()
|
defer s.rw.Unlock()
|
||||||
wsServer.BroadcastTo(s.Id, "session end")
|
wsServer.BroadcastTo(s.Id, "session end")
|
||||||
log.Printf("Starting clean up of session [%s]\n", s.Id)
|
log.Printf("Starting clean up of session [%s]\n", s.Id)
|
||||||
for _, i := range s.Instances {
|
for _, i := range s.Instances {
|
||||||
i.Conn.Close()
|
i.conn.Close()
|
||||||
if err := DeleteContainer(i.Name); err != nil {
|
if err := DeleteContainer(i.Name); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
@ -88,10 +98,39 @@ func NewSession() (*Session, error) {
|
|||||||
log.Println("ERROR NETWORKING")
|
log.Println("ERROR NETWORKING")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(sessionId string) *Session {
|
func GetSession(sessionId string) *Session {
|
||||||
return sessions[sessionId]
|
s := sessions[sessionId]
|
||||||
|
if s != nil {
|
||||||
|
for _, instance := range s.Instances {
|
||||||
|
if !instance.IsConnected() {
|
||||||
|
instance.SetSession(s)
|
||||||
|
go instance.Attach()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadSessionsFromDisk() error {
|
||||||
|
file, err := os.Open("./sessions.gob")
|
||||||
|
if err == nil {
|
||||||
|
decoder := gob.NewDecoder(file)
|
||||||
|
err = decoder.Decode(&sessions)
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveSessionsToDisk() error {
|
||||||
|
file, err := os.Create("./sessions.gob")
|
||||||
|
if err == nil {
|
||||||
|
encoder := gob.NewEncoder(file)
|
||||||
|
err = encoder.Encode(&sessions)
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user