예제 #1
0
// ListContainers that are running
func (api *DockerApi) ListContainers(imageName string) ([]string, error) {
	endpoint := api.configFile.DockerEndpoint
	baseUrl := strings.Join(
		[]string{
			endpoint,
			"containers",
			"json",
		},
		"/",
	)
	params := strings.Join(
		[]string{
			"all=true",
		},
		"&",
	)
	url := baseUrl + "?" + params
	Logger.Trace("ListContainers Api call:", url)

	resp, _ := http.Get(url)
	defer resp.Body.Close()

	if body, err := ioutil.ReadAll(resp.Body); err != nil {
		Logger.Error("Could not contact docker endpoint:", endpoint)
		return nil, err
	} else {
		switch resp.StatusCode {
		case 200:
			var jsonResult []ApiContainer
			if err := json.Unmarshal(body, &jsonResult); err != nil {
				Logger.Error("Error while trying to marshall result, body:", jsonResult, " - Error:", err)
				return nil, err
			} else {
				var ids []string
				for i := range jsonResult {
					if jsonResult[i].Image == imageName {
						ids = append(ids, jsonResult[i].Id)
					}
				}

				Logger.Debug("Containers found for", imageName, ids)
				return ids, nil
			}
		case 404:
			Logger.Warn("Bad param supplied to API")
		case 500:
			Logger.Error("Error while trying to communicate to docker endpoint:", endpoint)
		}
	}

	return nil, nil
}
예제 #2
0
func (api *DockerApi) StartContainer(id string) (*string, error) {
	endpoint := api.configFile.DockerEndpoint

	postBody := DockerHostConfig{}

	url := strings.Join(
		[]string{
			endpoint,
			"containers",
			id,
			"start",
		},
		"/",
	)
	Logger.Trace("StartContainer() - Api call to:", url)

	var jsonResult string
	bytesReader, _ := postBody.Bytes()

	resp, _ := http.Post(url, "text/json", bytes.NewReader(bytesReader))
	defer resp.Body.Close()

	if _, err := ioutil.ReadAll(resp.Body); err != nil {
		Logger.Error("Could not contact docker endpoint:", endpoint)
		return nil, err
	} else {
		switch resp.StatusCode {
		case 204:
			Logger.Info("Started container:", id)
			return &jsonResult, nil
		case 404:
			Logger.Warn("No such container")
		case 500:
			Logger.Error("Error while trying to communicate to docker endpoint:", endpoint)
		}

		msg := "Unexpected response code: " + string(resp.StatusCode)
		Logger.Error(msg)
		return nil, errors.New(msg)
	}
}
예제 #3
0
func (api *DockerApi) StopContainer(containerId string) (string, error) {
	endpoint := api.configFile.DockerEndpoint

	url := strings.Join(
		[]string{
			endpoint,
			"containers",
			containerId,
			"stop",
		},
		"/",
	)
	Logger.Trace("StopContainer() - Api call to:", url)

	resp, _ := http.Post(url, "text/json", nil)
	defer resp.Body.Close()

	if _, err := ioutil.ReadAll(resp.Body); err != nil {
		Logger.Error("Could not contact docker endpoint:", endpoint)
		return "", err
	} else {
		switch resp.StatusCode {
		case 204:
			Logger.Info("Stopped container:", containerId)
			return containerId, nil
		case 404:
			msg := "No such container"
			Logger.Warn(msg)
			return "", nil
		case 500:
			msg := "Error while trying to communicate to docker endpoint: " + endpoint
			Logger.Error(msg)
			return "", nil
		}

		msg := "Unexpected response code: " + string(resp.StatusCode)
		Logger.Error(msg)
		return "", errors.New(msg)
	}
}
예제 #4
0
func (cmd *LaunchCommand) Execute(args []string) error {
	Logger.Info("Launching Tests... in dir:", cmd.Options.Dir)
	Logger.Debug("Force:", cmd.Options.Force)

	var wg sync.WaitGroup // used for channels

	// Check Config and Buildfiles
	configFile, buildFile, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options)
	if err != nil {
		return err
	}

	dc := NewDockerApi(cmd.App.AppState.Meta, configFile, buildFile)
	dc.ShowInfo()

	if err := dc.createTestTemplates(*cmd.Options); err != nil {
		return err
	}
	Logger.Trace("Created test-flight templates.")

	// Register channel so we can watch for events as they happen
	eventsChannel := make(ApiChannel)
	go watchForEventsOn(eventsChannel)
	dc.RegisterChannel(eventsChannel)

	fqImageName := cmd.App.AppState.BuildFile.ImageName + ":" + buildFile.Tag

	getImageId := func() (string, error) {

		if image, err := dc.GetImageDetails(fqImageName); err != nil {
			return "", err
		} else { // response from endpoint (but could be 404)
			if image == nil { // doesn't exist
				if newImage, err := dc.CreateDockerImage(fqImageName, cmd.Options); err != nil {
					return "", err
				} else {
					return newImage, nil
				}
			} else { // exists
				if cmd.Options.Force { // recreate anyway
					if newImage, err := dc.CreateDockerImage(fqImageName, cmd.Options); err != nil {
						return "", err
					} else {
						return newImage, nil
					}
				} else { // warn user
					Logger.Warn("Will be starting a container from an image which already exists.")
					return image.Id, nil
				}
			}
			return image.Id, nil
		}
	}

	createAndStartContainerFrom := func(imageId string) error {
		if container, err := dc.CreateContainer(imageId); err != nil {
			return err
		} else {
			Logger.Trace("Docker Container to start:", container.Id)
			if _, err := dc.StartContainer(container.Id); err != nil {
				return err
			} else {
				wg.Add(1)
				containerChannel := dc.Attach(container.Id)
				go watchContainerOn(containerChannel, &wg)
				wg.Wait()
				return nil
			}
		}
	}

	if imageId, err := getImageId(); err != nil {
		return err
	} else {
		msg := "Launching docker container: " + imageId
		Logger.Console(msg)

		return createAndStartContainerFrom(imageId)
	}

	return nil
}
예제 #5
0
func (api *DockerApi) CreateContainer(fqImageName string) (*ApiPostResponse, error) {
	if _, err := api.DeleteContainer(fqImageName); err != nil {
		msg := "Error occured while trying to delete a container. " +
			" This attempt failed for the following reason:" + err.Error()

		Logger.Error(msg, err)
		return nil, err
	}

	endpoint := api.configFile.DockerEndpoint

	postBody := ApiPostRequest{
		Image:        fqImageName,
		OpenStdin:    true,
		AttachStdin:  false,
		AttachStdout: true,
		AttachStderr: true,
		Cmd:          api.buildFile.LaunchCmd,
	}

	url := FilePath(endpoint, "containers", "create")
	Logger.Trace("CreateContainer() - Api call to:", url)

	jsonResult := ApiPostResponse{}
	bytesReader, _ := postBody.Bytes()
	resp, _ := http.Post(url, "application/json", bytes.NewReader(bytesReader))
	defer resp.Body.Close()

	if body, err := ioutil.ReadAll(resp.Body); err != nil {
		Logger.Error("Could not contact docker endpoint:", endpoint)
		return nil, err
	} else {
		msg := "Unexpected response code: " + string(resp.StatusCode)

		switch resp.StatusCode {
		case 201:
			if err := json.Unmarshal(body, &jsonResult); err != nil {
				Logger.Error(err)
				return nil, err
			}
			Logger.Info("Created container:", fqImageName)
			return &jsonResult, nil
		case 404:
			msg = "No such container"
			Logger.Warn(msg)
		case 406:
			msg = "Impossible to attach (container not running)"
			Logger.Warn(msg)
		case 500:
			// noticed that when docker endpoint fails, it fails with just
			// status 500, no message back. Logs do show error though somewhat
			// slim details. i.e No command specified where command is really CMD :-(
			msg = "Server and/or API error while trying to communicate with docker " +
				"endpoint: " + endpoint + ". Could be malformed Dockerfile (template). " +
				"Check your remote docker endpoint logs."
			Logger.Error(msg)
		}

		return nil, errors.New(msg)
	}
}
예제 #6
0
// CreateDockerImage creates a docker image by first creating the Dockerfile
// in memory and then tars it, prior to sending it along to the docker
// endpoint.
// Note: once the image creation starts the only way to know if it succeeded
//       is to query for it again.
func (api *DockerApi) CreateDockerImage(fqImageName string, options *CommandOptions) (string, error) {
	Logger.Debug("Creating Docker image by attempting to build Dockerfile: " + fqImageName)

	tmplName := "Dockerfile" // file ext of `.tmpl` is implicit, see below

	var requiredDockerFile = RequiredFile{
		Name: "Test-Flight Dockerfile", FileName: tmplName, FileType: "f",
	}

	modeDir := func() string {
		if options.SingleFileMode {
			return "filemode"
		} else {
			return "dirmode"
		}
	}()

	dockerfileBuffer := bytes.NewBuffer(nil)
	tarbuf := bytes.NewBuffer(nil)

	// Dockerfile mem
	dockerfile := bufio.NewWriter(dockerfileBuffer)
	// Or create file
	templateOutputDir := getTemplateDir(api.configFile)
	Logger.Trace(templateOutputDir, requiredDockerFile.FileName)
	fileToCreate := FilePath(templateOutputDir.FileName, requiredDockerFile.FileName)
	Logger.Debug("Trying to build:", fileToCreate)
	dockerfileOut, err := os.Create(fileToCreate)
	if err != nil {
		Logger.Error("Could not write Dockerfile", err)
		return "", err
	}
	defer dockerfileOut.Close()

	// The directory where the templates used to create inventory and playbook
	templates := func() string {
		// could have been simpler but I want to log path at this level
		var templatePath string

		if api.configFile.UseSystemDockerTemplates {
			templatePath = FilePath(api.configFile.TestFlightAssets, "templates", "system")
		} else {
			templatePath = FilePath(api.configFile.TestFlightAssets, "templates", "user")
		}

		Logger.Debug("Using template dir:", templatePath)
		return templatePath
	}()

	// This is used below in `ExecuteTemplate()`
	templateInputDir := FilePath(templates, modeDir)

	pattern := filepath.Join(templateInputDir, requiredDockerFile.FileName+"*.tmpl")
	tmpl := template.Must(template.ParseGlob(pattern))

	// In Mem
	if err := tmpl.ExecuteTemplate(dockerfile, requiredDockerFile.FileName, *api.getTemplateVar()); err != nil {
		Logger.Error("template execution: %s", err)
		return "", err
	}

	// File
	if err := tmpl.ExecuteTemplate(dockerfileOut, requiredDockerFile.FileName, *api.getTemplateVar()); err != nil {
		Logger.Error("template execution: %s", err)
		return "", err
	}

	// For in-mem flush buffer here
	dockerfile.Flush()

	// Logger.Trace("Dockerfile buffer len", dockerfileBuffer.Len())
	Logger.Trace("Dockerfile:", dockerfileBuffer.String())
	// Logger.Info("Created Dockerfile: " + fqImageName)

	currTime := time.Now()

	// Add Dockerfile to archive, break out
	tr := tar.NewWriter(tarbuf)
	tr.WriteHeader(&tar.Header{
		Name:       "Dockerfile",
		Size:       int64(dockerfileBuffer.Len()),
		ModTime:    currTime,
		AccessTime: currTime,
		ChangeTime: currTime,
	})
	tr.Write(dockerfileBuffer.Bytes())

	// Add Context to archive
	// tar test directory
	TarDirectory(tr, api.meta.Dir, api.buildFile.Ignore)
	tr.Close()

	var wg sync.WaitGroup // used for channels
	wg.Add(1)

	Logger.Trace("Building image...")
	buildChannel := make(ContainerChannel)
	// go watchContainerOn(buildChannel, &wg)
	api.BuildImage(tarbuf, fqImageName, &buildChannel, &wg)
	// go CaptureUserCancel(&buildChannel)

	select {
	case value := <-buildChannel:
		switch value {
		case "ok":
			failMsg := "Docker Image [" + fqImageName + "] failed to build."
			// We got an ok but don't know if it is legit. Possible to get no error
			// but image will be nil (successful api call but image doesn't exist)
			if image, err := api.GetImageDetails(fqImageName); err != nil || image == nil {
				return "", errors.New(failMsg)
			} else {
				Logger.Info("Successfully built Docker image: " + fqImageName)
				return image.Id, nil
			}
		case "canceled":
			msg := "User Canceled docker creation."
			Logger.Warn(msg, "User must manually stop last running container.")
			return fqImageName, errors.New(msg)
		}
	}

	wg.Wait()

	Logger.Info("Successfully built Docker image: " + fqImageName)
	return fqImageName, nil
}
예제 #7
0
func (api *DockerApi) DeleteImage(name string) (string, error) {
	type ContainerStatus map[string]string

	status := func(statusMap ContainerStatus) string {
		possibleStatus := []string{"Untagged", "Deleted"}
		for _, v := range possibleStatus {
			containerId := statusMap[v]
			if containerId != "" {
				return containerId
			}
		}

		Logger.Error("Api out of sync, could not map status, using UNKNOWN")
		return "UNKNOWN"
	}

	endpoint := api.configFile.DockerEndpoint

	delete := func(imageId string) ([]string, error) {
		var deletedContainers []string

		baseUrl := strings.Join(
			[]string{
				endpoint,
				"images",
				imageId,
			},
			"/",
		)

		params := strings.Join(
			[]string{
				"force=true",
				"noprune=false",
			},
			"&",
		)
		url := baseUrl + "?" + params
		Logger.Trace("DeleteImage() Api call:", url)

		req, _ := http.NewRequest("DELETE", url, nil)
		resp, _ := http.DefaultClient.Do(req)
		defer resp.Body.Close()

		if body, err := ioutil.ReadAll(resp.Body); err != nil {
			Logger.Error("Could not contact docker endpoint:", endpoint)
		} else {
			switch resp.StatusCode {
			case 200:
				var jsonResult []ContainerStatus

				if err := json.Unmarshal(body, &jsonResult); err != nil {
					Logger.Error(err)
				} else {
					for _, v := range jsonResult {
						deletedContainers = append(deletedContainers, status(v))
					}

					return deletedContainers, nil
				}
			case 409:
				msg := "Cannot delete image while in use by a container. Delete the container first."
				Logger.Warn(msg)
				return deletedContainers, nil
			case 404:
				msg := "Image not found, cannot delete."
				Logger.Warn(msg)
				return deletedContainers, nil
			case 500:
				msg := "Error while trying to communicate to docker endpoint:"
				Logger.Error(msg)
				return nil, errors.New(msg)
			}

			msg := "API Out of sync, contact developers. Response code: " + strconv.Itoa(resp.StatusCode)
			Logger.Error(msg)
			return nil, errors.New(msg)
		}

		return deletedContainers, nil
	}

	// Need funcs.map(_.apply) where funcs is type [](func, errorString)
	if image, err := api.GetImageDetails(name); err != nil {
		Logger.Error("Could not get image details:", err)
		return "", nil
	} else if image != nil && err == nil {
		if _, err := delete(image.Id); err != nil {
			Logger.Error("Could not delete image: ", name, ", Id:", image.Id, ", Error was:", err)
			return "", err
		}

		return name, nil
	}

	return "", nil
}
예제 #8
0
// Returns container name deleted, empty string if none, and an
// error (if any)
func (api *DockerApi) DeleteContainer(name string) ([]DeletedContainer, error) {
	var deletedContainers []DeletedContainer
	var returnError error

	endpoint := api.configFile.DockerEndpoint

	delete := func(id string) (*string, error) {
		baseUrl := strings.Join(
			[]string{
				endpoint,
				"containers",
				id,
			},
			"/",
		)

		params := strings.Join(
			[]string{
				"v=true",
				"force=true",
			},
			"&",
		)
		url := baseUrl + "?" + params

		Logger.Trace("DeleteContainer() Api call:", url)

		req, _ := http.NewRequest("DELETE", url, nil)
		resp, _ := http.DefaultClient.Do(req)
		defer resp.Body.Close()

		if _, err := ioutil.ReadAll(resp.Body); err != nil {
			Logger.Error("Could not contact docker endpoint:", endpoint)
			return nil, err
		} else {
			switch resp.StatusCode {
			case 204:
				Logger.Info("Container Deleted:", name, ", with ID:", id)
				return &name, nil
			case 400:
				msg := "Bad Api param supplied while trying to delete a container, notify developers."
				Logger.Error(msg)
				return nil, errors.New(msg)
			case 404:
				Logger.Warn("Container not found, nothing to delete.")
				return nil, nil
			case 406:
				msg := "Container:" + name + " - (" + id + "), is running, cannot delete."
				Logger.Warn(msg)
				return nil, errors.New(msg)
			case 500:
				msg := "Error while trying to communicate to docker endpoint:" + endpoint
				// Logger.Error(msg)
				return nil, errors.New(msg)
			}

			statusCode := strconv.Itoa(resp.StatusCode)
			msg := "API out of sync, contact developers! Status Code:" + statusCode
			return nil, errors.New(msg)
		}
	}

	if images, err := api.ListContainers(name); err != nil {
		Logger.Error("Could not get image details:", err)
		return nil, err
	} else if images != nil {
		Logger.Info("Found", len(images), "to delete...")
		for _, container := range images {
			Logger.Debug("Trying to delete", container)
			if deleted, err := delete(container); err != nil {
				returnError = err
				msg := "Could not delete container: " + err.Error()
				Logger.Error(msg)
			} else if *deleted != "" {
				deletedContainers = append(deletedContainers, DeletedContainer{*deleted, container})
			}
		}
		return deletedContainers, returnError
	} else {
		Logger.Debug("No containers found for", name)
		return deletedContainers, returnError
	}
}