Beispiel #1
0
func (api *DockerApi) GetImageDetails(fqImageName string) (*ApiDockerImage, error) {
	url := strings.Join(
		[]string{
			api.configFile.DockerEndpoint,
			"images",
			fqImageName,
			"json",
		},
		"/",
	)

	// For testing, also need to `import "encoding/json"`
	// result := map[string]json.RawMessage{}
	result := ApiDockerImage{}

	Logger.Trace("GetImageDetails Api call to:", url)
	if resp, err := napping.Get(url, nil, &result, nil); err != nil {
		Logger.Error("Error while getting Image information from docker,", err)
		return nil, err
	} else {
		switch resp.Status() {
		case 200:
			Logger.Trace(result)
			return &result, nil
		case 404:
			Logger.Debug("Image not found")
			return nil, nil
		}

		return nil, nil
	}
}
Beispiel #2
0
func (api *DockerApi) Destroy(fqImageName string) error {
	if running, err := api.ListContainers(fqImageName); err != nil {
		Logger.Trace("Error while trying to get a list of containers for ", fqImageName)
		return err
	} else {
		for _, container := range running {
			if id, err := api.StopContainer(container); err != nil {
				Logger.Error("Could not stop container", id, " associated with", fqImageName)
				return err
			}
		}

		// Once all stopped, delete all
		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 err
		}

		if _, err := api.DeleteImage(fqImageName); err != nil {
			Logger.Error("Could not delete image,", err)
			return err
		}
	}

	return nil
}
Beispiel #3
0
/*
 * Reads the current directory and returns
 *   - bool: if all the required files were found
 */
func HasRequiredFiles(dir string, requiredFiles []RequiredFile) (bool, error) {
	if dir == "" {
		dir = defaultDir
	}

	filesFromDisk, err := ioutil.ReadDir(dir)

	if err != nil {
		return false, err
	}

	for _, requiredFile := range requiredFiles {
		if found, err := findFile(ConvertFiles(filesFromDisk), requiredFile, dir); err != nil {
			return false, err
		} else {
			if !found {
				msg := "Can't find " + requiredFile.Name + ": " + FilePath(dir, requiredFile.FileName)
				return false, errors.New(msg)
			} else {
				Logger.Trace("Found:", FilePath(dir, requiredFile.FileName))
			}
		}
	}

	return true, nil
}
Beispiel #4
0
// We only want to attach to containers associated with a particular
// Image name
func (api *DockerApi) Attach(containerId string) ContainerChannel {
	out := make(ContainerChannel) // channel to send back
	go CaptureUserCancel(&out, nil)

	go func() {
		endpoint := api.configFile.DockerEndpoint

		baseUrl := strings.Join(
			[]string{
				endpoint,
				"containers",
				containerId,
				"attach",
			},
			"/",
		)

		params := strings.Join(
			[]string{
				"stdout=true",
				"stderr=true",
				"stream=true",
			},
			"&",
		)
		url := baseUrl + "?" + params
		Logger.Trace("Attach() - Api call to:", url)

		// jsonResult := ""
		byteData := []byte{}
		bytesReader := bytes.NewReader(byteData)
		resp, err := http.Post(url, "text/json", bytesReader)
		defer resp.Body.Close()
		if err != nil {
			Logger.Error("Could not submit request, err:", err)
		}

		reader := bufio.NewReader(resp.Body)

		for {
			if line, err := reader.ReadBytes('\n'); err != nil {
				if err == io.EOF {
					break
				} else {
					msg := "Error reading from stream on attached container, error:" + err.Error()
					Logger.Error(msg)
					// close(out)
					// return error.New(msg)
				}
			} else {
				Logger.ConsoleChannel(string(bytes.TrimSpace(line)[:]))
			}
		}

		close(out)
	}()

	return out
}
// tries to find config file in user home, then if it cannot find one there
// will try to find a config file in the local running directory
func findConfig(dir string) (*ConfigFile, error) {
	configFileName := Constants().configFileName
	configFile := NewConfigFile()
	logConfigFile := func(configFile *ConfigFile) {
		Logger.Debug("Found config file.")
		Logger.Trace("Config file contents:", *configFile)
	}

	// try dir specified
	localConfigPath := FilePath(dir, configFileName)
	configFile, err := getConfig(localConfigPath)
	if err != nil {
		msg := "Config: " + localConfigPath + " may not exist or cannot be read. " + err.Error()
		Logger.Debug(msg)
	} else {
		logConfigFile(configFile)
		return configFile, nil
	}

	// try running directory next
	pwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	Logger.Debug("Checking for config file in local pwd: " + pwd + "/" + configFileName)
	pwdConfigPath := FilePath(pwd, ".test-flight", configFileName)
	configFile, err = getConfig(pwdConfigPath)
	if err != nil {
		msg := "Config: " + localConfigPath + " may not exist or cannot be read. " + err.Error()
		Logger.Debug(msg)
	} else {
		logConfigFile(configFile)
		return configFile, nil
	}

	// try home
	usr, err := user.Current() // to get user home, get user first
	if err != nil {
		Logger.Error("Can't read user home.")
		// return nil, ReadFileError.New("Can't read user home.")
		return nil, errors.New("Can't read user home.")
	}

	homeConfigPath := FilePath(usr.HomeDir, ".test-flight", configFileName)
	Logger.Debug("Checking for config file in user HOME: ", homeConfigPath)

	configFile, err = getConfig(homeConfigPath)
	if err != nil {
		msg := "Config: " + localConfigPath + " may not exist or cannot be read. " + err.Error()
		Logger.Debug(msg)
	} else {
		logConfigFile(configFile)
		return configFile, nil
	}

	return nil, errors.New("Cannot find config file, Please supply the config file.")
}
Beispiel #6
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
}
Beispiel #7
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)
	}
}
Beispiel #8
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)
	}
}
Beispiel #9
0
// Generates required files
// Note: complex types are free form and can be aything
//       where simple types are top level only.
func generateRequiredFilesFrom(buildfile *BuildFile) ([]RequiredFile, error) {
	requiredFiles := []RequiredFile{}

	for _, file := range buildfile.Add.Simple {
		name := "Ansible " + file + " dir"
		requiredFiles = append(requiredFiles, RequiredFile{Name: name, FileName: file, FileType: "d" /*,RequiredFiles: AnsibleFiles*/})
	}

	// Treat Complex as fully qualified paths and as OS Files
	for _, file := range buildfile.Add.Complex {
		if file.Name == "" || file.Location == "" {
			msg := "Found empty values for " + Constants().buildFileName + " Complex entry. Check syntax, try again."
			Logger.Error(msg)
			return nil, errors.New(msg)
		}

		name := "file " + file.Name
		requiredFiles = append(requiredFiles, RequiredFile{Name: name, FileName: file.Location, FileType: "f"})
	}

	Logger.Trace(requiredFiles)

	return requiredFiles, nil
}
Beispiel #10
0
func TarDirectory(tw *tar.Writer, dir string, ignoreList []string) error {
	Logger.Trace("Taring: ", dir)

	// Add Dockerfile to the ignore list
	// This can also help prevent existing Dockerfiles from being used
	// and accidently shipped in the context.
	ignoreList = append(ignoreList, "Dockerfile")

	shouldIgnore := func(file string) bool {
		for _, entry := range ignoreList {
			if entry == file {
				return true
			}
		}
		return false
	}

	var archive = func(files []os.FileInfo) error {
		Logger.Trace("Found files to archive into context: ", len(files))

		for _, file := range files {
			if shouldIgnore(file.Name()) {
				Logger.Debug("Ignoring:", file.Name())
				continue
			}

			fullFilePath := strings.Join([]string{dir, file.Name()}, "/")

			if file.IsDir() {
				TarDirectory(tw, fullFilePath, ignoreList)
				continue
			}

			hdr := &tar.Header{
				Name: fullFilePath,
				Size: file.Size(),
			}
			if err := tw.WriteHeader(hdr); err != nil {
				Logger.Error("Could not write context archive header", err)
				return err
			}

			if bytes, err := ioutil.ReadFile(fullFilePath); err != nil {
				Logger.Error("Could not read context file: ["+fullFilePath+"]", err)
				return err
			} else {
				if _, err := tw.Write(bytes); err != nil {
					Logger.Error("Could not archive context file: ["+fullFilePath+"]", err)
					return err
				}
			}

			Logger.Trace("Archived into context the file: [" + fullFilePath + "]")
		}

		Logger.Trace("Successfully archived context", dir)
		return nil
	}

	if filesFromDisk, err := ioutil.ReadDir(dir); err != nil {
		Logger.Error("Error while trying to tar ["+dir+"]", err)
		return err
	} else {
		return archive(filesFromDisk)
	}
}
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
}
Beispiel #12
0
func (api *DockerApi) BuildImage(
	buffer *bytes.Buffer,
	imageName string,
	watch *ContainerChannel,
	wg *sync.WaitGroup,
) {
	// out := make(ContainerChannel) // channel to send back
	go CaptureUserCancel(watch, wg)

	go func() error {
		endpoint := api.configFile.DockerEndpoint

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

		params := strings.Join(
			[]string{
				"t=" + imageName,
			},
			"&",
		)
		url := baseUrl + "?" + params
		Logger.Trace("BuildImage() - Api call to:", url)

		type BuildStream struct {
			Stream string
		}
		var jsonResult BuildStream

		bytesReader := bytes.NewReader(buffer.Bytes())
		resp, err := http.Post(url, "application/tar", bytesReader)
		defer resp.Body.Close()
		if err != nil {
			Logger.Error("Could not submit request, err:", err)
			return err
		}

		reader := bufio.NewReader(resp.Body)

		for {
			if line, err := reader.ReadBytes('\n'); err != nil {
				if err == io.EOF {
					break
				} else {
					msg := "Error reading from stream on building image, error:" + err.Error()
					Logger.Error(msg)
					*watch <- "ok"
					return errors.New(msg)
				}
			} else {
				if err := json.Unmarshal(line, &jsonResult); err != nil {
					Logger.Error(err)
				} else {
					Logger.ConsoleChannel(strings.TrimSpace(jsonResult.Stream))
				}
			}
		}

		*watch <- "ok"
		wg.Done()
		return nil
	}()
}
Beispiel #13
0
func watchForEventsOn(channel ApiChannel) {
	for msg := range channel {
		Logger.Trace("DOCKER EVENT:", *msg)
	}
}
Beispiel #14
0
func (appState *ApplicationState) SetState(newState string) string {
	appState.Meta.CurrentMode = newState
	Logger.Trace("STATE changed to", appState.Meta.CurrentMode)
	return appState.Meta.CurrentMode
}
Beispiel #15
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
	}
}
Beispiel #16
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)
	}
}
Beispiel #17
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
}
Beispiel #18
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
}