// PATCH /, Update the FS with a file change. func updateContainerHandler(rw http.ResponseWriter, req *http.Request) { // Get the fields required to do the path update. err := req.ParseMultipartForm(httpMaxMem) if err != nil { renderer.JSON(rw, http.StatusBadRequest, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } pathType := req.FormValue("pathtype") relPath := req.FormValue("path") typ := req.FormValue("type") modeStr := req.FormValue("mode") if relPath == "" || typ == "" { renderer.JSON(rw, http.StatusBadRequest, map[string]string{ "status": requests.StatusFailed, "error": "Missing form fields.", }) return } fullPath := filepath.Join(currentContainer.RemotePath, path.RelSystem(relPath)) // Container needs to exist. if currentContainer == nil { renderer.JSON(rw, http.StatusBadRequest, map[string]string{ "status": requests.StatusFailed, "error": delancey.ErrNotInUse.Error(), }) return } go logClient.Info("updating container", map[string]interface{}{ "container": currentContainer, "ip": agentHost, }) if typ == delancey.DeleteStatus { // Delete path from the service. err = os.RemoveAll(fullPath) if err != nil { renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } } else { // Create/Update path in the service. if pathType == "dir" { err = os.MkdirAll(fullPath, os.ModePerm|os.ModeDir) if err != nil { renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } } else { attach, _, err := req.FormFile("file") if err != nil { if err == http.ErrMissingFile { err = errors.New("Missing form fields.") } renderer.JSON(rw, http.StatusBadRequest, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } defer attach.Close() // Ensure parents exist. err = os.MkdirAll(filepath.Dir(fullPath), os.ModePerm|os.ModeDir) if err != nil { renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } dest, err := os.Create(fullPath) if err != nil { renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } defer dest.Close() // Copy updated contents to destination. _, err = io.Copy(dest, attach) if err != nil { renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } } // Set the file permissions if given. if modeStr != "" { mode, err := strconv.ParseUint(modeStr, 10, 32) if err != nil { renderer.JSON(rw, http.StatusBadRequest, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } err = os.Chmod(fullPath, os.FileMode(mode)) if err != nil { renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } } } renderer.JSON(rw, http.StatusOK, map[string]string{ "status": requests.StatusUpdated, }) }
// POST /, Create container. func createContainerHandler(rw http.ResponseWriter, req *http.Request) { // Only allow one container at a time. if currentContainer != nil { renderer.JSON(rw, http.StatusBadRequest, map[string]string{ "status": requests.StatusFailed, "error": delancey.ErrInUse.Error(), }) return } // Get container from body. containerReq := new(requests.DockerfileContainerReq) decoder := json.NewDecoder(req.Body) err := decoder.Decode(containerReq) if err != nil { renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } scontainer := containerReq.Container go logClient.Info("creating container", map[string]interface{}{ "container": scontainer, "ip": agentHost, }) // Create new Container. container, err := NewContainer(scontainer) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } image := config.DockerBaseImage + ":" + container.ImageID steps := float64(4) // Number of steps in the create progress. // Clean up if a failure occured. defer func() { if err == nil { return } if Env != "testing" && container.DockerID != "" { container.DeleteDocker() } container.DeletePaths() currentContainer = nil currentContainer.Save() }() if Env != "testing" { // Pull the image down to check if it exists. log.Println("Pulling down image", container.ImageID) progChan := make(chan float64) prevProg := float64(0) go func() { for prog := range progChan { progVal := float64(prog) / steps prevProg = progVal sendProgress("environment", progVal, fmt.Sprintf("container-%s", container.ID)) } }() err = quay.PullImage(DockerClient, image, progChan) if err != nil && !quay.IsNotFound(err) { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } // If the tag doesn't exist yet, create it from the base. if err != nil { // Create a container using the base. log.Println("Image doesn't exist", container.ImageID) // Set the prev since there was no progress done. prevProg = 1 / steps sendProgress("environment", prevProg, fmt.Sprintf("container-%s", container.ID)) // If no Dockerfile was given, just create the image from the base. if containerReq.Dockerfile == "" { err = createImage(container.ImageID, image, config.DockerBaseImage) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } prevProg = (1 / steps) + prevProg sendProgress("environment", prevProg, fmt.Sprintf("container-%s", container.ID)) } else { progChan := make(chan float64) lastProg := prevProg go func() { for prog := range progChan { prevProg = ((prog / 2) / steps) + lastProg sendProgress("environment", prevProg, fmt.Sprintf("container-%s", container.ID)) } }() // Use the given Dockerfile as the base image. log.Println("Building Dockerfile to image for", container.ImageID) _, err = buildImage(true, map[string]string{ "Dockerfile": containerReq.Dockerfile, }, nil, image, progChan) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } progChan = make(chan float64) lastProg = prevProg go func() { for prog := range progChan { prevProg = ((prog) / steps) + lastProg sendProgress("environment", prevProg, fmt.Sprintf("container-%s", container.ID)) } }() // Now we need to ensure sshd is installed and configured correctly. // To do this we build the image using itself as the base. log.Println("Building Dockerfile with SSH for", container.ImageID) _, err = buildImage(false, map[string]string{ "Dockerfile": sshDockerfile, }, map[string]string{ "baseimage": image, "sshdinstall": config.SSHInstallAddr, "sshdconfig": config.SSHConfigAddr, }, image, progChan) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } } } // Inspect the image to get any env vars. inspectedBase, err := DockerClient.InspectImage(image) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } user := "******" password := uuid.New() envVars := "" envVarsExport := "" // Copy the env vars to use as a file in the image build. if inspectedBase.Config.Env != nil { envVars = strings.Join(inspectedBase.Config.Env, "\n") for _, kv := range inspectedBase.Config.Env { envVarsExport += "export " + kv + "\n" } } // Build the image to use for the container, which sets the password. log.Println("Creating runner image for container", container.ImageID) image, err := buildImage(false, map[string]string{ "Dockerfile": passwordDockerfile, "bowery-env": envVars, "bowery-vars": envVarsExport, }, map[string]string{ "baseimage": image, "user": user, "password": password, "motdpath": config.EnvMessageAddr, }, config.DockerBaseImage, nil) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } prevProg = (1 / steps) + prevProg sendProgress("environment", prevProg, fmt.Sprintf("container-%s", container.ID)) container.ContainerPath = "/root/" + filepath.Base(path.RelSystem(container.LocalPath)) config := &docker.Config{ Volumes: map[string]string{ container.RemotePath: container.ContainerPath, container.SSHPath: "/root/.ssh", }, NetworkMode: "host", Privileged: true, } log.Println("Creating container", container.ImageID) id, err := DockerClient.Create(config, image, []string{"/usr/sbin/sshd", "-D"}) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } log.Println("Starting container", container.ImageID) err = DockerClient.Start(config, id) if err != nil { go logClient.Error(err.Error(), map[string]interface{}{ "container": scontainer, "ip": agentHost, }) renderer.JSON(rw, http.StatusInternalServerError, map[string]string{ "status": requests.StatusFailed, "error": err.Error(), }) return } log.Println("Container started", id, container.ImageID) prevProg = (1 / steps) + prevProg sendProgress("environment", prevProg, fmt.Sprintf("container-%s", container.ID)) container.User = user container.Password = password container.DockerID = id } err = nil currentContainer = container currentContainer.Save() renderer.JSON(rw, http.StatusOK, map[string]interface{}{ "status": requests.StatusCreated, "container": container, }) }