Files
play-with-docker/pwd/instance.go
Jonathan Leibiusky @xetorthio b0b9269ccc Allow to launch instances with any kind of public image.
Images that are not whitelisted will be launched as normal containers.
Only whitelisted ones will be launched as privileged.
Additionally pull the image if it doesn't exist.
2017-05-27 20:04:37 -03:00

277 lines
6.4 KiB
Go

package pwd
import (
"context"
"fmt"
"io"
"log"
"net"
"net/http"
"path/filepath"
"strings"
"sync"
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/docker"
"golang.org/x/text/encoding"
)
type sessionWriter struct {
sessionId string
instanceName string
broadcast BroadcastApi
}
func (s *sessionWriter) Write(p []byte) (n int, err error) {
s.broadcast.BroadcastTo(s.sessionId, "terminal out", s.instanceName, string(p))
return len(p), nil
}
type UInt16Slice []uint16
func (p UInt16Slice) Len() int { return len(p) }
func (p UInt16Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p UInt16Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type Instance struct {
Image string `json:"image"`
Name string `json:"name"`
Hostname string `json:"hostname"`
IP string `json:"ip"`
IsManager *bool `json:"is_manager"`
Mem string `json:"mem"`
Cpu string `json:"cpu"`
Alias string `json:"alias"`
ServerCert []byte `json:"server_cert"`
ServerKey []byte `json:"server_key"`
CACert []byte `json:"ca_cert"`
Cert []byte `json:"cert"`
Key []byte `json:"key"`
session *Session `json:"-"`
conn net.Conn `json:"-"`
ctx context.Context `json:"-"`
docker docker.DockerApi `json:"-"`
tempPorts []uint16 `json:"-"`
Ports UInt16Slice
rw sync.Mutex
}
type InstanceConfig struct {
ImageName string
Alias string
ServerCert []byte
ServerKey []byte
CACert []byte
Cert []byte
Key []byte
}
func (i *Instance) setUsedPort(port uint16) {
i.rw.Lock()
defer i.rw.Unlock()
for _, p := range i.tempPorts {
if p == port {
return
}
}
i.tempPorts = append(i.tempPorts, port)
}
func (i *Instance) IsConnected() bool {
return i.conn != nil
}
func (i *Instance) SetSession(s *Session) {
i.session = s
}
func (p *pwd) InstanceResizeTerminal(instance *Instance, rows, cols uint) error {
return p.docker.ContainerResize(instance.Name, rows, cols)
}
func (p *pwd) InstanceAttachTerminal(instance *Instance) error {
conn, err := p.docker.CreateAttachConnection(instance.Name)
if err != nil {
return err
}
encoder := encoding.Replacement.NewEncoder()
sw := &sessionWriter{sessionId: instance.session.Id, instanceName: instance.Name, broadcast: p.broadcast}
instance.conn = conn
io.Copy(encoder.Writer(sw), conn)
return nil
}
func (p *pwd) InstanceUploadFromUrl(instance *Instance, url string) error {
log.Printf("Downloading file [%s]\n", url)
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("Could not download file [%s]. Error: %s\n", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("Could not download file [%s]. Status code: %d\n", url, resp.StatusCode)
}
_, fileName := filepath.Split(url)
copyErr := p.docker.CopyToContainer(instance.Name, "/var/run/pwd/uploads", fileName, resp.Body)
if copyErr != nil {
return fmt.Errorf("Error while downloading file [%s]. Error: %s\n", url, copyErr)
}
return nil
}
func (p *pwd) InstanceGet(session *Session, name string) *Instance {
return session.Instances[name]
}
func (p *pwd) InstanceFindByIP(ip string) *Instance {
for _, s := range sessions {
for _, i := range s.Instances {
if i.IP == ip {
return i
}
}
}
return nil
}
func (p *pwd) InstanceFindByAlias(sessionPrefix, alias string) *Instance {
for id, s := range sessions {
if strings.HasPrefix(id, sessionPrefix) {
for _, i := range s.Instances {
if i.Alias == alias {
return i
}
}
}
}
return nil
}
func (p *pwd) InstanceDelete(session *Session, instance *Instance) error {
if instance.conn != nil {
instance.conn.Close()
}
err := p.docker.DeleteContainer(instance.Name)
if err != nil && !strings.Contains(err.Error(), "No such container") {
log.Println(err)
return err
}
p.broadcast.BroadcastTo(session.Id, "delete instance", instance.Name)
delete(session.Instances, instance.Name)
if err := p.storage.Save(); err != nil {
return err
}
setGauges()
return nil
}
func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, error) {
if conf.ImageName == "" {
conf.ImageName = config.GetDindImageName()
}
log.Printf("NewInstance - using image: [%s]\n", conf.ImageName)
var nodeName string
var containerName string
for i := 1; ; i++ {
nodeName = fmt.Sprintf("node%d", i)
containerName = fmt.Sprintf("%s_%s", session.Id[:8], nodeName)
exists := false
for _, instance := range session.Instances {
if instance.Name == containerName {
exists = true
break
}
}
if !exists {
break
}
}
opts := docker.CreateContainerOpts{
Image: conf.ImageName,
SessionId: session.Id,
PwdIpAddress: session.PwdIpAddress,
ContainerName: containerName,
Hostname: nodeName,
ServerCert: conf.ServerCert,
ServerKey: conf.ServerKey,
CACert: conf.CACert,
Privileged: false,
}
for _, imageName := range p.InstanceAllowedImages() {
if conf.ImageName == imageName {
opts.Privileged = true
break
}
}
ip, err := p.docker.CreateContainer(opts)
if err != nil {
return nil, err
}
instance := &Instance{}
instance.Image = opts.Image
instance.IP = ip
instance.Name = containerName
instance.Hostname = nodeName
instance.Alias = conf.Alias
instance.Cert = conf.Cert
instance.Key = conf.Key
instance.ServerCert = conf.ServerCert
instance.ServerKey = conf.ServerKey
instance.CACert = conf.CACert
instance.session = session
if session.Instances == nil {
session.Instances = make(map[string]*Instance)
}
session.Instances[instance.Name] = instance
go p.InstanceAttachTerminal(instance)
err = p.storage.Save()
if err != nil {
return nil, err
}
p.broadcast.BroadcastTo(session.Id, "new instance", instance.Name, instance.IP, instance.Hostname)
setGauges()
return instance, nil
}
func (p *pwd) InstanceWriteToTerminal(instance *Instance, data string) {
if instance != nil && instance.conn != nil && len(data) > 0 {
instance.conn.Write([]byte(data))
}
}
func (p *pwd) InstanceAllowedImages() []string {
return []string{
config.GetDindImageName(),
"franela/dind:overlay2-dev",
}
}
func (p *pwd) InstanceExec(instance *Instance, cmd []string) (int, error) {
return p.docker.Exec(instance.Name, cmd)
}