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.
277 lines
6.4 KiB
Go
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)
|
|
}
|