| 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/config" | |
| "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 | |
| } | |
| networks := []string{session.Id} | |
| if config.Unsafe { | |
| networks = append(networks, conf.Networks...) | |
| } | |
| 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: conf.Privileged, | |
| Networks: networks, | |
| 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 | |
| } | |