Files
play-with-docker/provisioner/dind.go
2020-06-19 08:26:38 -03:00

291 lines
7.5 KiB
Go

package provisioner
import (
"bytes"
"fmt"
"io"
"log"
"net"
"net/http"
"path/filepath"
"strings"
lru "github.com/hashicorp/golang-lru"
"github.com/play-with-docker/play-with-docker/docker"
"github.com/play-with-docker/play-with-docker/id"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/router"
"github.com/play-with-docker/play-with-docker/storage"
)
type DinD struct {
factory docker.FactoryApi
storage storage.StorageApi
generator id.Generator
cache *lru.Cache
}
func NewDinD(generator id.Generator, f docker.FactoryApi, s storage.StorageApi) *DinD {
c, _ := lru.New(5000)
return &DinD{generator: generator, factory: f, storage: s, cache: c}
}
func checkHostnameExists(sessionId, hostname string, instances []*types.Instance) bool {
exists := false
for _, instance := range instances {
if instance.Hostname == hostname {
exists = true
break
}
}
return exists
}
func (d *DinD) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) {
if conf.ImageName == "" {
playground, err := d.storage.PlaygroundGet(session.PlaygroundId)
if err != nil {
return nil, err
}
conf.ImageName = playground.DefaultDinDInstanceImage
}
log.Printf("NewInstance - using image: [%s]\n", conf.ImageName)
if conf.Hostname == "" {
instances, err := d.storage.InstanceFindBySessionId(session.Id)
if err != nil {
return nil, err
}
var nodeName string
for i := 1; ; i++ {
nodeName = fmt.Sprintf("node%d", i)
exists := checkHostnameExists(session.Id, nodeName, instances)
if !exists {
break
}
}
conf.Hostname = nodeName
}
containerName := fmt.Sprintf("%s_%s", session.Id[:8], d.generator.NewId())
opts := docker.CreateContainerOpts{
Image: conf.ImageName,
SessionId: session.Id,
ContainerName: containerName,
Hostname: conf.Hostname,
ServerCert: conf.ServerCert,
ServerKey: conf.ServerKey,
CACert: conf.CACert,
HostFQDN: conf.PlaygroundFQDN,
Privileged: true,
Networks: []string{session.Id},
DindVolumeSize: conf.DindVolumeSize,
Envs: conf.Envs,
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return nil, err
}
if err := dockerClient.ContainerCreate(opts); err != nil {
return nil, err
}
ips, err := dockerClient.ContainerIPs(containerName)
if err != nil {
return nil, err
}
instance := &types.Instance{}
instance.Image = opts.Image
instance.IP = ips[session.Id]
instance.RoutableIP = instance.IP
instance.SessionId = session.Id
instance.Name = containerName
instance.Hostname = conf.Hostname
instance.Cert = conf.Cert
instance.Key = conf.Key
instance.ServerCert = conf.ServerCert
instance.ServerKey = conf.ServerKey
instance.CACert = conf.CACert
instance.Tls = conf.Tls
instance.ProxyHost = router.EncodeHost(session.Id, instance.RoutableIP, router.HostOpts{})
instance.SessionHost = session.Host
return instance, nil
}
func (d *DinD) getSession(sessionId string) (*types.Session, error) {
var session *types.Session
if s, found := d.cache.Get(sessionId); !found {
s, err := d.storage.SessionGet(sessionId)
if err != nil {
return nil, err
}
session = s
d.cache.Add(sessionId, s)
} else {
session = s.(*types.Session)
}
return session, nil
}
func (d *DinD) InstanceDelete(session *types.Session, instance *types.Instance) error {
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return err
}
err = dockerClient.ContainerDelete(instance.Name)
if err != nil && !strings.Contains(err.Error(), "No such container") {
return err
}
return nil
}
func (d *DinD) InstanceExec(instance *types.Instance, cmd []string) (int, error) {
session, err := d.getSession(instance.SessionId)
if err != nil {
return -1, err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return -1, err
}
return dockerClient.Exec(instance.Name, cmd)
}
func (d *DinD) InstanceFSTree(instance *types.Instance) (io.Reader, error) {
session, err := d.getSession(instance.SessionId)
if err != nil {
return nil, err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return nil, err
}
b := bytes.NewBuffer([]byte{})
if c, err := dockerClient.ExecAttach(instance.Name, []string{"bash", "-c", `tree --noreport -J $HOME`}, b); c > 0 {
log.Println(b.String())
return nil, fmt.Errorf("Error %d trying list directories", c)
} else if err != nil {
return nil, err
}
return b, nil
}
func (d *DinD) InstanceFile(instance *types.Instance, filePath string) (io.Reader, error) {
session, err := d.getSession(instance.SessionId)
if err != nil {
return nil, err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return nil, err
}
return dockerClient.CopyFromContainer(instance.Name, filePath)
}
func (d *DinD) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) error {
session, err := d.getSession(instance.SessionId)
if err != nil {
return err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return err
}
return dockerClient.ContainerResize(instance.Name, rows, cols)
}
func (d *DinD) InstanceGetTerminal(instance *types.Instance) (net.Conn, error) {
session, err := d.getSession(instance.SessionId)
if err != nil {
return nil, err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return nil, err
}
return dockerClient.CreateAttachConnection(instance.Name)
}
func (d *DinD) InstanceUploadFromUrl(instance *types.Instance, fileName, dest, 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)
}
session, err := d.getSession(instance.SessionId)
if err != nil {
return err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return err
}
copyErr := dockerClient.CopyToContainer(instance.Name, dest, fileName, resp.Body)
if copyErr != nil {
return fmt.Errorf("Error while downloading file [%s]. Error: %s\n", url, copyErr)
}
return nil
}
func (d *DinD) getInstanceCWD(instance *types.Instance) (string, error) {
session, err := d.getSession(instance.SessionId)
if err != nil {
return "", err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return "", err
}
b := bytes.NewBufferString("")
if c, err := dockerClient.ExecAttach(instance.Name, []string{"bash", "-c", `pwdx $(</var/run/cwd)`}, b); c > 0 {
return "", fmt.Errorf("Error %d trying to get CWD", c)
} else if err != nil {
return "", err
}
cwd := strings.TrimSpace(strings.Split(b.String(), ":")[1])
return cwd, nil
}
func (d *DinD) InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error {
session, err := d.getSession(instance.SessionId)
if err != nil {
return err
}
dockerClient, err := d.factory.GetForSession(session)
if err != nil {
return err
}
var finalDest string
if filepath.IsAbs(dest) {
finalDest = dest
} else {
if cwd, err := d.getInstanceCWD(instance); err != nil {
return err
} else {
finalDest = fmt.Sprintf("%s/%s", cwd, dest)
}
}
copyErr := dockerClient.CopyToContainer(instance.Name, finalDest, fileName, reader)
if copyErr != nil {
return fmt.Errorf("Error while uploading file [%s]. Error: %s\n", fileName, copyErr)
}
return nil
}