mirror of
https://github.com/bingohuang/docker-labs.git
synced 2025-07-14 01:57:32 +08:00
commit
6a0e07aeec
@ -5,4 +5,5 @@ RUN apk add --no-cache git tmux py-pip apache2-utils vim \
|
||||
|
||||
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 (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/franela/play-with-docker/handlers"
|
||||
"github.com/franela/play-with-docker/services"
|
||||
@ -11,11 +12,17 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
server := services.CreateWSServer()
|
||||
|
||||
server.On("connection", handlers.WS)
|
||||
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.StrictSlash(false)
|
||||
|
||||
|
@ -12,7 +12,8 @@ type ViewPort struct {
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
SO socketio.Socket
|
||||
Id string
|
||||
so socketio.Socket
|
||||
ViewPort ViewPort
|
||||
}
|
||||
|
||||
@ -24,7 +25,7 @@ func (c *Client) ResizeViewPort(cols, rows uint) {
|
||||
func NewClient(so socketio.Socket, session *Session) *Client {
|
||||
so.Join(session.Id)
|
||||
|
||||
c := &Client{SO: so}
|
||||
c := &Client{so: so, Id: so.Id()}
|
||||
|
||||
so.On("session close", func() {
|
||||
CloseSession(session)
|
||||
@ -33,8 +34,8 @@ func NewClient(so socketio.Socket, session *Session) *Client {
|
||||
so.On("terminal in", func(name, data string) {
|
||||
// User wrote something on the terminal. Need to write it to the instance terminal
|
||||
instance := GetInstance(session, name)
|
||||
if instance != nil && len(data) > 0 {
|
||||
instance.Conn.Conn.Write([]byte(data))
|
||||
if instance != nil && instance.conn != nil && len(data) > 0 {
|
||||
instance.conn.Conn.Write([]byte(data))
|
||||
}
|
||||
})
|
||||
|
||||
@ -53,13 +54,13 @@ func NewClient(so socketio.Socket, session *Session) *Client {
|
||||
})
|
||||
so.On("disconnection", func() {
|
||||
// Client has disconnected. Remove from session and recheck terminal sizes.
|
||||
for i, cl := range session.Clients {
|
||||
if cl.SO.Id() == c.SO.Id() {
|
||||
session.Clients = append(session.Clients[:i], session.Clients[i+1:]...)
|
||||
for i, cl := range session.clients {
|
||||
if cl.Id == c.Id {
|
||||
session.clients = append(session.clients[:i], session.clients[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(session.Clients) > 0 {
|
||||
if len(session.clients) > 0 {
|
||||
vp := session.GetSmallestViewPort()
|
||||
// Resize all terminals in the session
|
||||
wsServer.BroadcastTo(session.Id, "viewport resize", vp.Cols, vp.Rows)
|
||||
|
@ -55,20 +55,10 @@ func DeleteNetwork(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateExecConnection(id string, ctx context.Context) (string, 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)
|
||||
func CreateAttachConnection(id string, ctx context.Context) (*types.HijackedResponse, error) {
|
||||
|
||||
conf := types.ContainerAttachOptions{true, true, true, true, "ctrl-x,ctrl-x", true}
|
||||
conn, err := c.ContainerAttach(ctx, id, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -76,8 +66,8 @@ func AttachExecConnection(execId string, ctx context.Context) (*types.HijackedRe
|
||||
return &conn, nil
|
||||
}
|
||||
|
||||
func ResizeExecConnection(execId string, ctx context.Context, cols, rows uint) error {
|
||||
return c.ContainerExecResize(ctx, execId, types.ResizeOptions{Height: rows, Width: cols})
|
||||
func ResizeConnection(name string, cols, rows uint) error {
|
||||
return c.ContainerResize(context.Background(), name, types.ResizeOptions{Height: rows, Width: cols})
|
||||
}
|
||||
|
||||
func CreateInstance(net string, dindImage string) (*Instance, error) {
|
||||
@ -88,7 +78,7 @@ func CreateInstance(net string, dindImage string) (*Instance, error) {
|
||||
t := true
|
||||
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, "")
|
||||
|
||||
if err != nil {
|
||||
|
@ -5,20 +5,31 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
var rw sync.Mutex
|
||||
|
||||
type Instance struct {
|
||||
Session *Session `json:"-"`
|
||||
session *Session `json:"-"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
Conn *types.HijackedResponse `json:"-"`
|
||||
ExecId string `json:"-"`
|
||||
Ctx context.Context `json:"-"`
|
||||
conn *types.HijackedResponse `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
|
||||
@ -43,14 +54,18 @@ func NewInstance(session *Session) (*Instance, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance.Session = session
|
||||
instance.session = session
|
||||
|
||||
if session.Instances == nil {
|
||||
session.Instances = make(map[string]*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)
|
||||
|
||||
@ -62,28 +77,23 @@ type sessionWriter struct {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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() {
|
||||
i.Ctx = context.Background()
|
||||
func (i *Instance) Attach() {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
i.Conn = conn
|
||||
i.conn = conn
|
||||
|
||||
go func() {
|
||||
encoder := encoding.Replacement.NewEncoder()
|
||||
@ -92,10 +102,9 @@ func (i *Instance) Exec() {
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-i.Ctx.Done():
|
||||
case <-i.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
func GetInstance(session *Session, name string) *Instance {
|
||||
//TODO: Use redis
|
||||
return session.Instances[name]
|
||||
|
@ -1,8 +1,10 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -13,17 +15,25 @@ import (
|
||||
var wsServer *socketio.Server
|
||||
|
||||
type Session struct {
|
||||
sync.Mutex
|
||||
rw sync.Mutex
|
||||
Id string `json:"id"`
|
||||
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 {
|
||||
minRows := s.Clients[0].ViewPort.Rows
|
||||
minCols := s.Clients[0].ViewPort.Cols
|
||||
minRows := s.clients[0].ViewPort.Rows
|
||||
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)))
|
||||
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) {
|
||||
s.Clients = append(s.Clients, c)
|
||||
s.clients = append(s.clients, c)
|
||||
}
|
||||
|
||||
var sessions map[string]*Session
|
||||
@ -51,12 +61,12 @@ func CreateWSServer() *socketio.Server {
|
||||
}
|
||||
|
||||
func CloseSession(s *Session) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.rw.Lock()
|
||||
defer s.rw.Unlock()
|
||||
wsServer.BroadcastTo(s.Id, "session end")
|
||||
log.Printf("Starting clean up of session [%s]\n", s.Id)
|
||||
for _, i := range s.Instances {
|
||||
i.Conn.Close()
|
||||
i.conn.Close()
|
||||
if err := DeleteContainer(i.Name); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
@ -88,10 +98,39 @@ func NewSession() (*Session, error) {
|
||||
log.Println("ERROR NETWORKING")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
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