Пример #1
0
// 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,
	})
}
Пример #2
0
// 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,
	})
}