func (c *ShellClient) AwaitContainerHealth(containerID string, timeout *time.Duration) error {
	c.logger.DebugWith("Awaiting container health", "containerID", containerID, "timeout", timeout)
	if !containerIDRegex.MatchString(containerID) && !restrictedNameRegex.MatchString(containerID) {
		return errors.New("Invalid container ID to await health for")
	}
	timedOut := false
	containerHealthy := make(chan error, 1)
	var timeoutChan <-chan time.Time
	if timeout == nil {
		timeoutChan = make(<-chan time.Time, 1)
	} else {
		timeoutChan = time.After(*timeout)
	}
	go func() {
		inspectInterval := 100 * time.Millisecond
		for !timedOut {
			containers, err := c.GetContainers(&GetContainerOptions{
				ID:      containerID,
				Stopped: true,
			})
			if err == nil && len(containers) > 0 {
				container := containers[0]
				if container.State.Health.Status == "healthy" {
					containerHealthy <- nil
					return
				}
				if container.State.Status == "exited" {
					containerHealthy <- errors.Errorf("Container exited with status: %d", container.State.ExitCode)
					return
				}
				if container.State.Status == "dead" {
					containerHealthy <- errors.New("Container seems to be dead")
					return
				}
				c.logger.DebugWith("Container not healthy yet, retrying soon",
					"timeout", timeout,
					"containerID", containerID,
					"containerState", container.State,
					"nextCheckIn", inspectInterval)
			}
			time.Sleep(inspectInterval)
			if inspectInterval < 800*time.Millisecond {
				inspectInterval *= 2
			}
		}
	}()
	select {
	case err := <-containerHealthy:
		if err != nil {
			return errors.Wrapf(err, "Container %s is not healthy", containerID)
		}
		c.logger.DebugWith("Container is healthy", "containerID", containerID)
	case <-timeoutChan:
		timedOut = true
		containerLogs, err := c.GetContainerLogs(containerID)
		if err != nil {
			c.logger.ErrorWith("Container wasn't healthy within timeout (failed to get logs)",
				"containerID", containerID,
				"timeout", timeout,
				"err", err)
		} else {
			c.logger.WarnWith("Container wasn't healthy within timeout",
				"containerID", containerID,
				"timeout", timeout,
				"logs", containerLogs)
		}
		return errors.New("Container wasn't healthy in time")
	}
	return nil
}
AwaitContainerHealth blocks until the given container is healthy or the timeout passes
package dockerclient
import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strings"
	"time"
	"github.com/nuclio/nuclio/pkg/cmdrunner"
	"github.com/nuclio/nuclio/pkg/common"
	"github.com/docker/distribution/reference"
	"github.com/nuclio/errors"
	"github.com/nuclio/logger"
	"k8s.io/apimachinery/pkg/util/json"
)
const restrictedNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
var restrictedNameRegex = regexp.MustCompile(`^/?` + restrictedNameChars + `+$`)
var containerIDRegex = regexp.MustCompile(`^[\w+-\.]+$`)
var restrictedBuildArgRegex = regexp.MustCompile(`^[\S]+$`)
var volumeNameRegex = regexp.MustCompile(`^[\S]+$`)
var envVarNameRegex = regexp.MustCompile(`^[^=]+$`)
type ShellClient struct {
	logger             logger.Logger
	cmdRunner          cmdrunner.CmdRunner
	redactedValues     []string
	buildTimeout       time.Duration
	buildRetryInterval time.Duration
}
func NewShellClient(parentLogger logger.Logger, runner cmdrunner.CmdRunner) (*ShellClient, error) {
	var err error
	newClient := &ShellClient{
		logger:             parentLogger.GetChild("docker"),
		cmdRunner:          runner,
		buildTimeout:       1 * time.Hour,
		buildRetryInterval: 3 * time.Second,
	}
	if newClient.cmdRunner == nil {
		newClient.cmdRunner, err = cmdrunner.NewShellRunner(newClient.logger)
		if err != nil {
			return nil, errors.Wrap(err, "Failed to create command runner")
		}
	}
	if _, err := newClient.GetVersion(false); err != nil {
		return nil, errors.Wrap(err, "No docker client found")
	}
	return newClient, nil
}
func (c *ShellClient) Build(buildOptions *BuildOptions) error {
	c.logger.DebugWith("Building image", "buildOptions", buildOptions)
	if err := c.validateBuildOptions(buildOptions); err != nil {
		return errors.Wrap(err, "Invalid build options passed")
	}
	if buildOptions.ContextDir == "" && buildOptions.DockerfilePath != "" {
		buildOptions.ContextDir = path.Dir(buildOptions.DockerfilePath)
	}
	if buildOptions.DockerfilePath == "" && buildOptions.ContextDir != "" {
		buildOptions.DockerfilePath = path.Join(buildOptions.ContextDir, "Dockerfile")
	}
	buildArgs := ""
	for buildArgName, buildArgValue := range buildOptions.BuildArgs {
		buildArgs += fmt.Sprintf("--build-arg %s=%s ", buildArgName, buildArgValue)
	}
	if err := c.build(buildOptions, buildArgs); err != nil {
		return errors.Wrap(err, "Failed to build")
	}
	c.logger.DebugWith("Successfully built image", "image", buildOptions.Image)
	return nil
}
func (c *ShellClient) CopyObjectsFromImage(imageName string,
	objectsToCopy map[string]string,
	allowCopyErrors bool) error {
	containerID, err := c.createContainer(imageName)
	if err != nil {
		return errors.Wrapf(err, "Failed to create container from %s", imageName)
	}
	defer c.runCommand(nil, "docker rm -f %s", containerID)  
	for objectImagePath, objectLocalPath := range objectsToCopy {
		_, err = c.runCommand(nil, "docker cp %s:%s %s", containerID, objectImagePath, objectLocalPath)
		if err != nil && !allowCopyErrors {
			return errors.Wrapf(err, "Can't copy %s:%s -> %s", containerID, objectImagePath, objectLocalPath)
		}
	}
	return nil
}
func (c *ShellClient) PushImage(imageName string, registryURL string) error {
	taggedImage := common.CompileImageName(registryURL, imageName)
	c.logger.InfoWith("Pushing image", "from", imageName, "to", taggedImage)
	if _, err := reference.Parse(imageName); err != nil {
		return errors.Wrap(err, "Invalid image name to tag/push")
	}
	if _, err := reference.Parse(taggedImage); err != nil {
		return errors.Wrap(err, "Invalid tagged image name to tag/push")
	}
	_, err := c.runCommand(nil, "docker tag %s %s", imageName, taggedImage)
	if err != nil {
		return errors.Wrap(err, "Failed to tag image")
	}
	_, err = c.runCommand(nil, "docker push %s", taggedImage)
	if err != nil {
		return errors.Wrap(err, "Failed to push image")
	}
	return nil
}
func (c *ShellClient) PullImage(imageURL string) error {
	c.logger.InfoWith("Pulling image", "imageName", imageURL)
	if _, err := reference.Parse(imageURL); err != nil {
		return errors.Wrap(err, "Invalid image URL to pull")
	}
	_, err := c.runCommand(nil, "docker pull %s", imageURL)
	return err
}
func (c *ShellClient) RemoveImage(imageName string) error {
	c.logger.DebugWith("Removing image", "imageName", imageName)
	if _, err := reference.Parse(imageName); err != nil {
		return errors.Wrap(err, "Invalid image name to remove")
	}
	_, err := c.runCommand(nil, "docker rmi -f %s", imageName)
	return err
}
func (c *ShellClient) RunContainer(imageName string, runOptions *RunOptions) (string, error) {
	c.logger.DebugWith("Running container", "imageName", imageName, "runOptions", runOptions)
	if err := c.validateRunOptions(imageName, runOptions); err != nil {
		return "", errors.Wrap(err, "Invalid run options passed")
	}
	var dockerArguments []string
	for localPort, dockerPort := range runOptions.Ports {
		if localPort == RunOptionsNoPort {
			dockerArguments = append(dockerArguments, fmt.Sprintf("-p %d", dockerPort))
		} else {
			dockerArguments = append(dockerArguments, fmt.Sprintf("-p %d:%d", localPort, dockerPort))
		}
	}
	if runOptions.RestartPolicy != nil && runOptions.RestartPolicy.Name != RestartPolicyNameNo {
		if runOptions.Remove {
			return "", errors.Errorf("Cannot combine restart policy with container removal")
		}
		restartMaxRetries := runOptions.RestartPolicy.MaximumRetryCount
		restartPolicy := fmt.Sprintf("--restart %s", runOptions.RestartPolicy.Name)
		if runOptions.RestartPolicy.Name == RestartPolicyNameOnFailure && restartMaxRetries >= 0 {
			restartPolicy += fmt.Sprintf(":%d", restartMaxRetries)
		}
		dockerArguments = append(dockerArguments, restartPolicy)
	}
	if !runOptions.Attach {
		dockerArguments = append(dockerArguments, "-d")
	}
	if runOptions.GPUs != "" {
		dockerArguments = append(dockerArguments, fmt.Sprintf("--gpus %s", runOptions.GPUs))
	}
	if runOptions.Remove {
		dockerArguments = append(dockerArguments, "--rm")
	}
	if runOptions.ContainerName != "" {
		dockerArguments = append(dockerArguments, fmt.Sprintf("--name %s", runOptions.ContainerName))
	}
	if runOptions.Network != "" {
		dockerArguments = append(dockerArguments, fmt.Sprintf("--net %s", runOptions.Network))
	}
	if runOptions.Labels != nil {
		for labelName, labelValue := range runOptions.Labels {
			dockerArguments = append(dockerArguments,
				fmt.Sprintf("--label %s='%s'", labelName, c.replaceSingleQuotes(labelValue)))
		}
	}
	if runOptions.Env != nil {
		for envName, envValue := range runOptions.Env {
			dockerArguments = append(dockerArguments, fmt.Sprintf("--env %s='%s'", envName, envValue))
		}
	}
	if runOptions.Volumes != nil {
		for volumeHostPath, volumeContainerPath := range runOptions.Volumes {
			dockerArguments = append(dockerArguments,
				fmt.Sprintf("--volume %s:%s ", volumeHostPath, volumeContainerPath))
		}
	}
	if len(runOptions.MountPoints) > 0 {
		for _, mountPoint := range runOptions.MountPoints {
			readonly := ""
			if !mountPoint.RW {
				readonly = ",readonly"
			}
			dockerArguments = append(dockerArguments,
				fmt.Sprintf("--mount source=%s,destination=%s%s",
					mountPoint.Source,
					mountPoint.Destination,
					readonly))
		}
	}
	if runOptions.RunAsUser != nil || runOptions.RunAsGroup != nil {
		userStr := ""
		if runOptions.RunAsUser != nil {
			userStr += fmt.Sprintf("%d", *runOptions.RunAsUser)
		}
		if runOptions.RunAsGroup != nil {
			userStr += fmt.Sprintf(":%d", *runOptions.RunAsGroup)
		}
		dockerArguments = append(dockerArguments, fmt.Sprintf("--user %s", userStr))
	}
	if runOptions.FSGroup != nil {
		dockerArguments = append(dockerArguments, fmt.Sprintf("--group-add %d", *runOptions.FSGroup))
	}
	runResult, err := c.cmdRunner.Run(
		&cmdrunner.RunOptions{LogRedactions: c.redactedValues},
		"docker run %s %s %s",
		strings.Join(dockerArguments, " "),
		imageName,
		runOptions.Command)
	if err != nil {
		c.logger.WarnWith("Failed to run container",
			"err", err,
			"stdout", runResult.Output,
			"stderr", runResult.Stderr)
		return "", err
	}
	if runOptions.Stdout != nil {
		*runOptions.Stdout = runResult.Output
	}
	if runOptions.Stderr != nil {
		*runOptions.Stderr = runResult.Stderr
	}
	stdoutLines := strings.Split(runResult.Output, "\n")
	lastStdoutLine := c.getLastNonEmptyLine(stdoutLines, 0)
	if strings.Contains(lastStdoutLine, " ") {
		if !runOptions.ImageMayNotExist {
			return "", fmt.Errorf("Output from docker command includes more than just ID: %s", lastStdoutLine)
		}
		lastStdoutLine = c.getLastNonEmptyLine(stdoutLines, 1)
	}
	return lastStdoutLine, err
}
func (c *ShellClient) ExecInContainer(containerID string, execOptions *ExecOptions) error {
	c.logger.DebugWith("Executing in container", "containerID", containerID, "execOptions", execOptions)
	if err := c.validateExecOptions(containerID, execOptions); err != nil {
		return errors.Wrap(err, "Invalid exec options passed")
	}
	envArgument := ""
	if execOptions.Env != nil {
		for envName, envValue := range execOptions.Env {
			envArgument += fmt.Sprintf("--env %s='%s' ", envName, envValue)
		}
	}
	runResult, err := c.cmdRunner.Run(
		&cmdrunner.RunOptions{LogRedactions: c.redactedValues},
		"docker exec %s %s %s",
		envArgument,
		containerID,
		execOptions.Command)
	if err != nil {
		c.logger.DebugWith("Failed to execute command in container",
			"err", err,
			"stdout", runResult.Output,
			"stderr", runResult.Stderr)
		return err
	}
	if execOptions.Stdout != nil {
		*execOptions.Stdout = runResult.Output
	}
	if execOptions.Stderr != nil {
		*execOptions.Stderr = runResult.Stderr
	}
	return nil
}
func (c *ShellClient) RemoveContainer(containerID string) error {
	c.logger.DebugWith("Removing container", "containerID", containerID)
	if !containerIDRegex.MatchString(containerID) && !restrictedNameRegex.MatchString(containerID) {
		return errors.New("Invalid container ID name in remove container")
	}
	_, err := c.runCommand(nil, "docker rm -f %s", containerID)
	return err
}
func (c *ShellClient) StopContainer(containerID string) error {
	c.logger.DebugWith("Stopping container", "containerID", containerID)
	if !containerIDRegex.MatchString(containerID) && !restrictedNameRegex.MatchString(containerID) {
		return errors.New("Invalid container ID to stop")
	}
	_, err := c.runCommand(nil, "docker stop %s", containerID)
	return err
}
func (c *ShellClient) StartContainer(containerID string) error {
	c.logger.DebugWith("Starting container", "containerID", containerID)
	if !containerIDRegex.MatchString(containerID) && !restrictedNameRegex.MatchString(containerID) {
		return errors.New("Invalid container ID to start")
	}
	_, err := c.runCommand(nil, "docker start %s", containerID)
	return err
}
func (c *ShellClient) GetContainerLogs(containerID string) (string, error) {
	c.logger.DebugWith("Getting container logs", "containerID", containerID)
	if !containerIDRegex.MatchString(containerID) && !restrictedNameRegex.MatchString(containerID) {
		return "", errors.New("Invalid container ID to get logs from")
	}
	runOptions := &cmdrunner.RunOptions{
		CaptureOutputMode: cmdrunner.CaptureOutputModeCombined,
	}
	runResult, err := c.runCommand(runOptions, "docker logs %s", containerID)
	return runResult.Output, err
}