github.imxd.top/openshift/source-to-image@v1.2.0/pkg/docker/docker.go (about)

     1  package docker
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"math/rand"
    12  	"net/http"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strings"
    18  	"syscall"
    19  	"time"
    20  
    21  	dockertypes "github.com/docker/docker/api/types"
    22  	dockercontainer "github.com/docker/docker/api/types/container"
    23  	dockernetwork "github.com/docker/docker/api/types/network"
    24  	dockerapi "github.com/docker/docker/client"
    25  	dockermessage "github.com/docker/docker/pkg/jsonmessage"
    26  	dockerstdcopy "github.com/docker/docker/pkg/stdcopy"
    27  	"github.com/docker/go-connections/tlsconfig"
    28  	"golang.org/x/net/context"
    29  
    30  	"github.com/openshift/source-to-image/pkg/api"
    31  	"github.com/openshift/source-to-image/pkg/api/constants"
    32  	s2ierr "github.com/openshift/source-to-image/pkg/errors"
    33  	s2itar "github.com/openshift/source-to-image/pkg/tar"
    34  	"github.com/openshift/source-to-image/pkg/util"
    35  	"github.com/openshift/source-to-image/pkg/util/fs"
    36  	"github.com/openshift/source-to-image/pkg/util/interrupt"
    37  )
    38  
    39  const (
    40  	// DefaultDestination is the destination where the artifacts will be placed
    41  	// if DestinationLabel was not specified.
    42  	DefaultDestination = "/tmp"
    43  	// DefaultTag is the image tag, being applied if none is specified.
    44  	DefaultTag = "latest"
    45  
    46  	// DefaultDockerTimeout specifies a timeout for Docker API calls. When this
    47  	// timeout is reached, certain Docker API calls might error out.
    48  	DefaultDockerTimeout = 2 * time.Minute
    49  
    50  	// DefaultShmSize is the default shared memory size to use (in bytes) if not specified.
    51  	DefaultShmSize = int64(1024 * 1024 * 64)
    52  	// DefaultPullRetryDelay is the default pull image retry interval
    53  	DefaultPullRetryDelay = 5 * time.Second
    54  	// DefaultPullRetryCount is the default pull image retry times
    55  	DefaultPullRetryCount = 6
    56  )
    57  
    58  var (
    59  	// RetriableErrors is a set of strings that indicate that an retriable error occurred.
    60  	RetriableErrors = []string{
    61  		"ping attempt failed with error",
    62  		"is already in progress",
    63  		"connection reset by peer",
    64  		"transport closed before response was received",
    65  		"connection refused",
    66  	}
    67  )
    68  
    69  // containerNamePrefix prefixes the name of containers launched by S2I. We
    70  // cannot reuse the prefix "k8s" because we don't want the containers to be
    71  // managed by a kubelet.
    72  const containerNamePrefix = "s2i"
    73  
    74  // containerName creates names for Docker containers launched by S2I. It is
    75  // meant to resemble Kubernetes' pkg/kubelet/dockertools.BuildDockerName.
    76  func containerName(image string) string {
    77  	//Initialize seed
    78  	rand.Seed(time.Now().UnixNano())
    79  	uid := fmt.Sprintf("%08x", rand.Uint32())
    80  	// Replace invalid characters for container name with underscores.
    81  	image = strings.Map(func(r rune) rune {
    82  		if ('0' <= r && r <= '9') || ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') {
    83  			return r
    84  		}
    85  		return '_'
    86  	}, image)
    87  	return fmt.Sprintf("%s_%s_%s", containerNamePrefix, image, uid)
    88  }
    89  
    90  // Docker is the interface between STI and the docker engine-api.
    91  // It contains higher level operations called from the STI
    92  // build or usage commands
    93  type Docker interface {
    94  	IsImageInLocalRegistry(name string) (bool, error)
    95  	IsImageOnBuild(string) bool
    96  	GetOnBuild(string) ([]string, error)
    97  	RemoveContainer(id string) error
    98  	GetScriptsURL(name string) (string, error)
    99  	GetAssembleInputFiles(string) (string, error)
   100  	GetAssembleRuntimeUser(string) (string, error)
   101  	RunContainer(opts RunContainerOptions) error
   102  	GetImageID(name string) (string, error)
   103  	GetImageWorkdir(name string) (string, error)
   104  	CommitContainer(opts CommitContainerOptions) (string, error)
   105  	RemoveImage(name string) error
   106  	CheckImage(name string) (*api.Image, error)
   107  	PullImage(name string) (*api.Image, error)
   108  	CheckAndPullImage(name string) (*api.Image, error)
   109  	BuildImage(opts BuildImageOptions) error
   110  	GetImageUser(name string) (string, error)
   111  	GetImageEntrypoint(name string) ([]string, error)
   112  	GetLabels(name string) (map[string]string, error)
   113  	UploadToContainer(fs fs.FileSystem, srcPath, destPath, container string) error
   114  	UploadToContainerWithTarWriter(fs fs.FileSystem, srcPath, destPath, container string, makeTarWriter func(io.Writer) s2itar.Writer) error
   115  	DownloadFromContainer(containerPath string, w io.Writer, container string) error
   116  	Version() (dockertypes.Version, error)
   117  	CheckReachable() error
   118  }
   119  
   120  // Client contains all methods used when interacting directly with docker engine-api
   121  type Client interface {
   122  	ContainerAttach(ctx context.Context, container string, options dockertypes.ContainerAttachOptions) (dockertypes.HijackedResponse, error)
   123  	ContainerCommit(ctx context.Context, container string, options dockertypes.ContainerCommitOptions) (dockertypes.IDResponse, error)
   124  	ContainerCreate(ctx context.Context, config *dockercontainer.Config, hostConfig *dockercontainer.HostConfig, networkingConfig *dockernetwork.NetworkingConfig, containerName string) (dockercontainer.ContainerCreateCreatedBody, error)
   125  	ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error)
   126  	ContainerRemove(ctx context.Context, container string, options dockertypes.ContainerRemoveOptions) error
   127  	ContainerStart(ctx context.Context, container string, options dockertypes.ContainerStartOptions) error
   128  	ContainerKill(ctx context.Context, container, signal string) error
   129  	ContainerWait(ctx context.Context, container string, condition dockercontainer.WaitCondition) (<-chan dockercontainer.ContainerWaitOKBody, <-chan error)
   130  	CopyToContainer(ctx context.Context, container, path string, content io.Reader, opts dockertypes.CopyToContainerOptions) error
   131  	CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, dockertypes.ContainerPathStat, error)
   132  	ImageBuild(ctx context.Context, buildContext io.Reader, options dockertypes.ImageBuildOptions) (dockertypes.ImageBuildResponse, error)
   133  	ImageInspectWithRaw(ctx context.Context, image string) (dockertypes.ImageInspect, []byte, error)
   134  	ImagePull(ctx context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error)
   135  	ImageRemove(ctx context.Context, image string, options dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error)
   136  	ServerVersion(ctx context.Context) (dockertypes.Version, error)
   137  }
   138  
   139  type stiDocker struct {
   140  	client   Client
   141  	pullAuth dockertypes.AuthConfig
   142  }
   143  
   144  // InspectImage returns the image information and its raw representation.
   145  func (d stiDocker) InspectImage(name string) (*dockertypes.ImageInspect, error) {
   146  	ctx, cancel := getDefaultContext()
   147  	defer cancel()
   148  	resp, _, err := d.client.ImageInspectWithRaw(ctx, name)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	return &resp, nil
   153  }
   154  
   155  // PostExecutor is an interface which provides a PostExecute function
   156  type PostExecutor interface {
   157  	PostExecute(containerID, destination string) error
   158  }
   159  
   160  // PullResult is the result returned by the PullImage function
   161  type PullResult struct {
   162  	OnBuild bool
   163  	Image   *api.Image
   164  }
   165  
   166  // RunContainerOptions are options passed in to the RunContainer method
   167  type RunContainerOptions struct {
   168  	Image           string
   169  	PullImage       bool
   170  	PullAuth        api.AuthConfig
   171  	ExternalScripts bool
   172  	ScriptsURL      string
   173  	Destination     string
   174  	Env             []string
   175  	AddHost         []string
   176  	// Entrypoint will be used to override the default entrypoint
   177  	// for the image if it has one.  If the image has no entrypoint,
   178  	// this value is ignored.
   179  	Entrypoint       []string
   180  	Stdin            io.ReadCloser
   181  	Stdout           io.WriteCloser
   182  	Stderr           io.WriteCloser
   183  	OnStart          func(containerID string) error
   184  	PostExec         PostExecutor
   185  	TargetImage      bool
   186  	NetworkMode      string
   187  	User             string
   188  	CGroupLimits     *api.CGroupLimits
   189  	CapDrop          []string
   190  	Binds            []string
   191  	Command          string
   192  	CommandOverrides func(originalCmd string) string
   193  	// CommandExplicit provides a full control on the CMD directive.
   194  	// It won't modified in any way and will be passed to the docker as-is.
   195  	// Use this option when you want to use arbitrary command as CMD directive.
   196  	// In this case you can't use Command because 1) it's just a string
   197  	// 2) it will be modified by prepending base dir and cleaned by the path.Join().
   198  	// You also can't use CommandOverrides because 1) it's a string
   199  	// 2) it only gets applied when Command equals to "assemble" or "usage" script
   200  	// AND script is inside of the tar archive.
   201  	CommandExplicit []string
   202  	// SecurityOpt is passed through as security options to the underlying container.
   203  	SecurityOpt []string
   204  }
   205  
   206  // asDockerConfig converts a RunContainerOptions into a Config understood by the
   207  // docker client
   208  func (rco RunContainerOptions) asDockerConfig() dockercontainer.Config {
   209  	return dockercontainer.Config{
   210  		Image:        getImageName(rco.Image),
   211  		User:         rco.User,
   212  		Env:          rco.Env,
   213  		Entrypoint:   rco.Entrypoint,
   214  		OpenStdin:    rco.Stdin != nil,
   215  		StdinOnce:    rco.Stdin != nil,
   216  		AttachStdout: rco.Stdout != nil,
   217  	}
   218  }
   219  
   220  // asDockerHostConfig converts a RunContainerOptions into a HostConfig
   221  // understood by the docker client
   222  func (rco RunContainerOptions) asDockerHostConfig() dockercontainer.HostConfig {
   223  	hostConfig := dockercontainer.HostConfig{
   224  		CapDrop:         rco.CapDrop,
   225  		PublishAllPorts: rco.TargetImage,
   226  		NetworkMode:     dockercontainer.NetworkMode(rco.NetworkMode),
   227  		Binds:           rco.Binds,
   228  		ExtraHosts:      rco.AddHost,
   229  		SecurityOpt:     rco.SecurityOpt,
   230  	}
   231  	if rco.CGroupLimits != nil {
   232  		hostConfig.Resources.Memory = rco.CGroupLimits.MemoryLimitBytes
   233  		hostConfig.Resources.MemorySwap = rco.CGroupLimits.MemorySwap
   234  		hostConfig.Resources.CgroupParent = rco.CGroupLimits.Parent
   235  	}
   236  	return hostConfig
   237  }
   238  
   239  // asDockerCreateContainerOptions converts a RunContainerOptions into a
   240  // ContainerCreateConfig understood by the docker client
   241  func (rco RunContainerOptions) asDockerCreateContainerOptions() dockertypes.ContainerCreateConfig {
   242  	config := rco.asDockerConfig()
   243  	hostConfig := rco.asDockerHostConfig()
   244  	return dockertypes.ContainerCreateConfig{
   245  		Name:       containerName(rco.Image),
   246  		Config:     &config,
   247  		HostConfig: &hostConfig,
   248  	}
   249  }
   250  
   251  // asDockerAttachToContainerOptions converts a RunContainerOptions into a
   252  // ContainerAttachOptions understood by the docker client
   253  func (rco RunContainerOptions) asDockerAttachToContainerOptions() dockertypes.ContainerAttachOptions {
   254  	return dockertypes.ContainerAttachOptions{
   255  		Stdin:  rco.Stdin != nil,
   256  		Stdout: rco.Stdout != nil,
   257  		Stderr: rco.Stderr != nil,
   258  		Stream: rco.Stdout != nil,
   259  	}
   260  }
   261  
   262  // CommitContainerOptions are options passed in to the CommitContainer method
   263  type CommitContainerOptions struct {
   264  	ContainerID string
   265  	Repository  string
   266  	User        string
   267  	Command     []string
   268  	Env         []string
   269  	Entrypoint  []string
   270  	Labels      map[string]string
   271  }
   272  
   273  // BuildImageOptions are options passed in to the BuildImage method
   274  type BuildImageOptions struct {
   275  	Name         string
   276  	Stdin        io.Reader
   277  	Stdout       io.WriteCloser
   278  	CGroupLimits *api.CGroupLimits
   279  }
   280  
   281  // NewEngineAPIClient creates a new Docker engine API client
   282  func NewEngineAPIClient(config *api.DockerConfig) (*dockerapi.Client, error) {
   283  	var httpClient *http.Client
   284  
   285  	if config.UseTLS || config.TLSVerify {
   286  		tlscOptions := tlsconfig.Options{
   287  			InsecureSkipVerify: !config.TLSVerify,
   288  		}
   289  
   290  		if _, err := os.Stat(config.CAFile); !os.IsNotExist(err) {
   291  			tlscOptions.CAFile = config.CAFile
   292  		}
   293  		if _, err := os.Stat(config.CertFile); !os.IsNotExist(err) {
   294  			tlscOptions.CertFile = config.CertFile
   295  		}
   296  		if _, err := os.Stat(config.KeyFile); !os.IsNotExist(err) {
   297  			tlscOptions.KeyFile = config.KeyFile
   298  		}
   299  
   300  		tlsc, err := tlsconfig.Client(tlscOptions)
   301  		if err != nil {
   302  			return nil, err
   303  		}
   304  
   305  		httpClient = &http.Client{
   306  			Transport: &http.Transport{
   307  				TLSClientConfig: tlsc,
   308  			},
   309  		}
   310  	}
   311  	return dockerapi.NewClient(config.Endpoint, os.Getenv("DOCKER_API_VERSION"), httpClient, nil)
   312  }
   313  
   314  // New creates a new implementation of the STI Docker interface
   315  func New(client Client, auth api.AuthConfig) Docker {
   316  	return &stiDocker{
   317  		client: client,
   318  		pullAuth: dockertypes.AuthConfig{
   319  			Username:      auth.Username,
   320  			Password:      auth.Password,
   321  			Email:         auth.Email,
   322  			ServerAddress: auth.ServerAddress,
   323  		},
   324  	}
   325  }
   326  
   327  func getDefaultContext() (context.Context, context.CancelFunc) {
   328  	// the intention is: all docker API calls with the exception of known long-
   329  	// running calls (ContainerWait, ImagePull, ImageBuild, ImageCommit) must complete within a
   330  	// certain timeout otherwise we bail.
   331  	return context.WithTimeout(context.Background(), DefaultDockerTimeout)
   332  }
   333  
   334  // GetImageWorkdir returns the WORKDIR property for the given image name.
   335  // When the WORKDIR is not set or empty, return "/" instead.
   336  func (d *stiDocker) GetImageWorkdir(name string) (string, error) {
   337  	resp, err := d.InspectImage(name)
   338  	if err != nil {
   339  		return "", err
   340  	}
   341  	workdir := resp.Config.WorkingDir
   342  	if len(workdir) == 0 {
   343  		// This is a default destination used by UploadToContainer when the WORKDIR
   344  		// is not set or it is empty. To show user where the injections will end up,
   345  		// we set this to "/".
   346  		workdir = "/"
   347  	}
   348  	return workdir, nil
   349  }
   350  
   351  // GetImageEntrypoint returns the ENTRYPOINT property for the given image name.
   352  func (d *stiDocker) GetImageEntrypoint(name string) ([]string, error) {
   353  	image, err := d.InspectImage(name)
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  	return image.Config.Entrypoint, nil
   358  }
   359  
   360  // UploadToContainer uploads artifacts to the container.
   361  func (d *stiDocker) UploadToContainer(fs fs.FileSystem, src, dest, container string) error {
   362  	makeWorldWritable := func(writer io.Writer) s2itar.Writer {
   363  		return s2itar.ChmodAdapter{Writer: tar.NewWriter(writer), NewFileMode: 0666, NewExecFileMode: 0666, NewDirMode: 0777}
   364  	}
   365  
   366  	return d.UploadToContainerWithTarWriter(fs, src, dest, container, makeWorldWritable)
   367  }
   368  
   369  // UploadToContainerWithTarWriter uploads artifacts to the container.
   370  // If the source is a directory, then all files and sub-folders are copied into
   371  // the destination (which has to be directory as well).
   372  // If the source is a single file, then the file copied into destination (which
   373  // has to be full path to a file inside the container).
   374  func (d *stiDocker) UploadToContainerWithTarWriter(fs fs.FileSystem, src, dest, container string, makeTarWriter func(io.Writer) s2itar.Writer) error {
   375  	destPath := filepath.Dir(dest)
   376  	r, w := io.Pipe()
   377  	go func() {
   378  		tarWriter := makeTarWriter(w)
   379  		tarWriter = s2itar.RenameAdapter{Writer: tarWriter, Old: filepath.Base(src), New: filepath.Base(dest)}
   380  
   381  		err := s2itar.New(fs).CreateTarStreamToTarWriter(src, true, tarWriter, nil)
   382  		if err == nil {
   383  			err = tarWriter.Close()
   384  		}
   385  
   386  		w.CloseWithError(err)
   387  	}()
   388  	log.V(3).Infof("Uploading %q to %q ...", src, destPath)
   389  	ctx, cancel := getDefaultContext()
   390  	defer cancel()
   391  	err := d.client.CopyToContainer(ctx, container, destPath, r, dockertypes.CopyToContainerOptions{})
   392  	if err != nil {
   393  		log.V(0).Infof("error: Uploading to container failed: %v", err)
   394  	}
   395  	return err
   396  }
   397  
   398  // DownloadFromContainer downloads file (or directory) from the container.
   399  func (d *stiDocker) DownloadFromContainer(containerPath string, w io.Writer, container string) error {
   400  	ctx, cancel := getDefaultContext()
   401  	defer cancel()
   402  	readCloser, _, err := d.client.CopyFromContainer(ctx, container, containerPath)
   403  	if err != nil {
   404  		return err
   405  	}
   406  	defer readCloser.Close()
   407  	_, err = io.Copy(w, readCloser)
   408  	return err
   409  }
   410  
   411  // IsImageInLocalRegistry determines whether the supplied image is in the local registry.
   412  func (d *stiDocker) IsImageInLocalRegistry(name string) (bool, error) {
   413  	name = getImageName(name)
   414  	resp, err := d.InspectImage(name)
   415  	if resp != nil {
   416  		return true, nil
   417  	}
   418  	if err != nil && !dockerapi.IsErrNotFound(err) {
   419  		return false, s2ierr.NewInspectImageError(name, err)
   420  	}
   421  	return false, nil
   422  }
   423  
   424  // GetImageUser finds and retrieves the user associated with
   425  // an image if one has been specified
   426  func (d *stiDocker) GetImageUser(name string) (string, error) {
   427  	name = getImageName(name)
   428  	resp, err := d.InspectImage(name)
   429  	if err != nil {
   430  		log.V(4).Infof("error inspecting image %s: %v", name, err)
   431  		return "", s2ierr.NewInspectImageError(name, err)
   432  	}
   433  	user := resp.Config.User
   434  	return user, nil
   435  }
   436  
   437  // Version returns information of the docker client and server host
   438  func (d *stiDocker) Version() (dockertypes.Version, error) {
   439  	ctx, cancel := getDefaultContext()
   440  	defer cancel()
   441  	return d.client.ServerVersion(ctx)
   442  }
   443  
   444  // IsImageOnBuild provides information about whether the Docker image has
   445  // OnBuild instruction recorded in the Image Config.
   446  func (d *stiDocker) IsImageOnBuild(name string) bool {
   447  	onbuild, err := d.GetOnBuild(name)
   448  	return err == nil && len(onbuild) > 0
   449  }
   450  
   451  // GetOnBuild returns the set of ONBUILD Dockerfile commands to execute
   452  // for the given image
   453  func (d *stiDocker) GetOnBuild(name string) ([]string, error) {
   454  	name = getImageName(name)
   455  	resp, err := d.InspectImage(name)
   456  	if err != nil {
   457  		log.V(4).Infof("error inspecting image %s: %v", name, err)
   458  		return nil, s2ierr.NewInspectImageError(name, err)
   459  	}
   460  	return resp.Config.OnBuild, nil
   461  }
   462  
   463  // CheckAndPullImage pulls an image into the local registry if not present
   464  // and returns the image metadata
   465  func (d *stiDocker) CheckAndPullImage(name string) (*api.Image, error) {
   466  	name = getImageName(name)
   467  	displayName := name
   468  
   469  	if !log.Is(3) {
   470  		// For less verbose log levels (less than 3), shorten long iamge names like:
   471  		//     "centos/php-56-centos7@sha256:51c3e2b08bd9fadefccd6ec42288680d6d7f861bdbfbd2d8d24960621e4e27f5"
   472  		// to include just enough characters to differentiate the build from others in the docker repository:
   473  		//     "centos/php-56-centos7@sha256:51c3e2b08bd..."
   474  		// 18 characters is somewhat arbitrary, but should be enough to avoid a name collision.
   475  		split := strings.Split(name, "@")
   476  		if len(split) > 1 && len(split[1]) > 18 {
   477  			displayName = split[0] + "@" + split[1][:18] + "..."
   478  		}
   479  	}
   480  
   481  	image, err := d.CheckImage(name)
   482  	if err != nil && !strings.Contains(err.(s2ierr.Error).Details.Error(), "No such image") {
   483  		return nil, err
   484  	}
   485  	if image == nil {
   486  		log.V(1).Infof("Image %q not available locally, pulling ...", displayName)
   487  		return d.PullImage(name)
   488  	}
   489  
   490  	log.V(3).Infof("Using locally available image %q", displayName)
   491  	return image, nil
   492  }
   493  
   494  // CheckImage checks image from the local registry.
   495  func (d *stiDocker) CheckImage(name string) (*api.Image, error) {
   496  	name = getImageName(name)
   497  	inspect, err := d.InspectImage(name)
   498  	if err != nil {
   499  		log.V(4).Infof("error inspecting image %s: %v", name, err)
   500  		return nil, s2ierr.NewInspectImageError(name, err)
   501  	}
   502  	if inspect != nil {
   503  		image := &api.Image{}
   504  		updateImageWithInspect(image, inspect)
   505  		return image, nil
   506  	}
   507  
   508  	return nil, nil
   509  }
   510  
   511  func base64EncodeAuth(auth dockertypes.AuthConfig) (string, error) {
   512  	var buf bytes.Buffer
   513  	if err := json.NewEncoder(&buf).Encode(auth); err != nil {
   514  		return "", err
   515  	}
   516  	return base64.URLEncoding.EncodeToString(buf.Bytes()), nil
   517  }
   518  
   519  // PullImage pulls an image into the local registry
   520  func (d *stiDocker) PullImage(name string) (*api.Image, error) {
   521  	name = getImageName(name)
   522  
   523  	// RegistryAuth is the base64 encoded credentials for the registry
   524  	base64Auth, err := base64EncodeAuth(d.pullAuth)
   525  	if err != nil {
   526  		return nil, s2ierr.NewPullImageError(name, err)
   527  	}
   528  	var retriableError = false
   529  
   530  	for retries := 0; retries <= DefaultPullRetryCount; retries++ {
   531  		err = util.TimeoutAfter(DefaultDockerTimeout, fmt.Sprintf("pulling image %q", name), func(timer *time.Timer) error {
   532  			resp, pullErr := d.client.ImagePull(context.Background(), name, dockertypes.ImagePullOptions{RegistryAuth: base64Auth})
   533  			if pullErr != nil {
   534  				return pullErr
   535  			}
   536  			defer resp.Close()
   537  
   538  			decoder := json.NewDecoder(resp)
   539  			for {
   540  				if !timer.Stop() {
   541  					return &util.TimeoutError{}
   542  				}
   543  				timer.Reset(DefaultDockerTimeout)
   544  
   545  				var msg dockermessage.JSONMessage
   546  				pullErr = decoder.Decode(&msg)
   547  				if pullErr == io.EOF {
   548  					return nil
   549  				}
   550  				if pullErr != nil {
   551  					return pullErr
   552  				}
   553  
   554  				if msg.Error != nil {
   555  					return msg.Error
   556  				}
   557  				if msg.Progress != nil {
   558  					log.V(4).Infof("pulling image %s: %s", name, msg.Progress.String())
   559  				}
   560  			}
   561  		})
   562  		if err == nil {
   563  			break
   564  		}
   565  		log.V(0).Infof("pulling image error : %v", err)
   566  		errMsg := fmt.Sprintf("%s", err)
   567  		for _, errorString := range RetriableErrors {
   568  			if strings.Contains(errMsg, errorString) {
   569  				retriableError = true
   570  				break
   571  			}
   572  		}
   573  
   574  		if !retriableError {
   575  			return nil, s2ierr.NewPullImageError(name, err)
   576  		}
   577  
   578  		log.V(0).Infof("retrying in %s ...", DefaultPullRetryDelay)
   579  		time.Sleep(DefaultPullRetryDelay)
   580  	}
   581  
   582  	inspectResp, err := d.InspectImage(name)
   583  	if err != nil {
   584  		return nil, s2ierr.NewPullImageError(name, err)
   585  	}
   586  	if inspectResp != nil {
   587  		image := &api.Image{}
   588  		updateImageWithInspect(image, inspectResp)
   589  		return image, nil
   590  	}
   591  	return nil, nil
   592  }
   593  
   594  func updateImageWithInspect(image *api.Image, inspect *dockertypes.ImageInspect) {
   595  	image.ID = inspect.ID
   596  	if inspect.Config != nil {
   597  		image.Config = &api.ContainerConfig{
   598  			Labels: inspect.Config.Labels,
   599  			Env:    inspect.Config.Env,
   600  		}
   601  	}
   602  	if inspect.ContainerConfig != nil {
   603  		image.ContainerConfig = &api.ContainerConfig{
   604  			Labels: inspect.ContainerConfig.Labels,
   605  			Env:    inspect.ContainerConfig.Env,
   606  		}
   607  	}
   608  }
   609  
   610  // RemoveContainer removes a container and its associated volumes.
   611  func (d *stiDocker) RemoveContainer(id string) error {
   612  	ctx, cancel := getDefaultContext()
   613  	defer cancel()
   614  	opts := dockertypes.ContainerRemoveOptions{
   615  		RemoveVolumes: true,
   616  	}
   617  	return d.client.ContainerRemove(ctx, id, opts)
   618  }
   619  
   620  // KillContainer kills a container.
   621  func (d *stiDocker) KillContainer(id string) error {
   622  	ctx, cancel := getDefaultContext()
   623  	defer cancel()
   624  	return d.client.ContainerKill(ctx, id, "SIGKILL")
   625  }
   626  
   627  // GetLabels retrieves the labels of the given image.
   628  func (d *stiDocker) GetLabels(name string) (map[string]string, error) {
   629  	name = getImageName(name)
   630  	resp, err := d.InspectImage(name)
   631  	if err != nil {
   632  		log.V(4).Infof("error inspecting image %s: %v", name, err)
   633  		return nil, s2ierr.NewInspectImageError(name, err)
   634  	}
   635  	return resp.Config.Labels, nil
   636  }
   637  
   638  // getImageName checks the image name and adds DefaultTag if none is specified
   639  func getImageName(name string) string {
   640  	_, tag, id := parseRepositoryTag(name)
   641  	if len(tag) == 0 && len(id) == 0 {
   642  		//_, tag, _ := parseRepositoryTag(name)
   643  		//if len(tag) == 0 {
   644  		return strings.Join([]string{name, DefaultTag}, ":")
   645  	}
   646  
   647  	return name
   648  }
   649  
   650  // getLabel gets label's value from the image metadata
   651  func getLabel(image *api.Image, name string) string {
   652  	if value, ok := image.Config.Labels[name]; ok {
   653  		return value
   654  	}
   655  	return ""
   656  }
   657  
   658  // getVariable gets environment variable's value from the image metadata
   659  func getVariable(image *api.Image, name string) string {
   660  	envName := name + "="
   661  	for _, v := range image.Config.Env {
   662  		if strings.HasPrefix(v, envName) {
   663  			return strings.TrimSpace(v[len(envName):])
   664  		}
   665  	}
   666  
   667  	return ""
   668  }
   669  
   670  // GetScriptsURL finds a scripts-url label on the given image.
   671  func (d *stiDocker) GetScriptsURL(image string) (string, error) {
   672  	imageMetadata, err := d.CheckAndPullImage(image)
   673  	if err != nil {
   674  		return "", err
   675  	}
   676  
   677  	return getScriptsURL(imageMetadata), nil
   678  }
   679  
   680  // GetAssembleInputFiles finds a io.openshift.s2i.assemble-input-files label on the given image.
   681  func (d *stiDocker) GetAssembleInputFiles(image string) (string, error) {
   682  	imageMetadata, err := d.CheckAndPullImage(image)
   683  	if err != nil {
   684  		return "", err
   685  	}
   686  
   687  	label := getLabel(imageMetadata, constants.AssembleInputFilesLabel)
   688  	if len(label) == 0 {
   689  		log.V(0).Infof("warning: Image %q does not contain a value for the %s label", image, constants.AssembleInputFilesLabel)
   690  	} else {
   691  		log.V(3).Infof("Image %q contains %s set to %q", image, constants.AssembleInputFilesLabel, label)
   692  	}
   693  	return label, nil
   694  }
   695  
   696  // GetAssembleRuntimeUser finds a io.openshift.s2i.assemble-runtime-user label on the given image.
   697  func (d *stiDocker) GetAssembleRuntimeUser(image string) (string, error) {
   698  	imageMetadata, err := d.CheckAndPullImage(image)
   699  	if err != nil {
   700  		return "", err
   701  	}
   702  	return getLabel(imageMetadata, constants.AssembleRuntimeUserLabel), nil
   703  }
   704  
   705  // getScriptsURL finds a scripts url label in the image metadata
   706  func getScriptsURL(image *api.Image) string {
   707  	if image == nil {
   708  		return ""
   709  	}
   710  	scriptsURL := getLabel(image, constants.ScriptsURLLabel)
   711  
   712  	// For backward compatibility, support the old label schema
   713  	if len(scriptsURL) == 0 {
   714  		scriptsURL = getLabel(image, constants.DeprecatedScriptsURLLabel)
   715  		if len(scriptsURL) > 0 {
   716  			log.V(0).Infof("warning: Image %s uses deprecated label '%s', please migrate it to %s instead!",
   717  				image.ID, constants.DeprecatedScriptsURLLabel, constants.ScriptsURLLabel)
   718  		}
   719  	}
   720  	if len(scriptsURL) == 0 {
   721  		scriptsURL = getVariable(image, constants.ScriptsURLEnvironment)
   722  		if len(scriptsURL) != 0 {
   723  			log.V(0).Infof("warning: Image %s uses deprecated environment variable %s, please migrate it to %s label instead!",
   724  				image.ID, constants.ScriptsURLEnvironment, constants.ScriptsURLLabel)
   725  		}
   726  	}
   727  	if len(scriptsURL) == 0 {
   728  		log.V(0).Infof("warning: Image %s does not contain a value for the %s label", image.ID, constants.ScriptsURLLabel)
   729  	} else {
   730  		log.V(2).Infof("Image %s contains %s set to %q", image.ID, constants.ScriptsURLLabel, scriptsURL)
   731  	}
   732  
   733  	return scriptsURL
   734  }
   735  
   736  // getDestination finds a destination label in the image metadata
   737  func getDestination(image *api.Image) string {
   738  	if val := getLabel(image, constants.DestinationLabel); len(val) != 0 {
   739  		return val
   740  	}
   741  	// For backward compatibility, support the old label schema
   742  	if val := getLabel(image, constants.DeprecatedDestinationLabel); len(val) != 0 {
   743  		log.V(0).Infof("warning: Image %s uses deprecated label '%s', please migrate it to %s instead!",
   744  			image.ID, constants.DeprecatedDestinationLabel, constants.DestinationLabel)
   745  		return val
   746  	}
   747  	if val := getVariable(image, constants.LocationEnvironment); len(val) != 0 {
   748  		log.V(0).Infof("warning: Image %s uses deprecated environment variable %s, please migrate it to %s label instead!",
   749  			image.ID, constants.LocationEnvironment, constants.DestinationLabel)
   750  		return val
   751  	}
   752  
   753  	// default directory if none is specified
   754  	return DefaultDestination
   755  }
   756  
   757  func constructCommand(opts RunContainerOptions, imageMetadata *api.Image, tarDestination string) []string {
   758  	// base directory for all S2I commands
   759  	commandBaseDir := determineCommandBaseDir(opts, imageMetadata, tarDestination)
   760  
   761  	// NOTE: We use path.Join instead of filepath.Join to avoid converting the
   762  	// path to UNC (Windows) format as we always run this inside container.
   763  	binaryToRun := path.Join(commandBaseDir, opts.Command)
   764  
   765  	// when calling assemble script with Stdin parameter set (the tar file)
   766  	// we need to first untar the whole archive and only then call the assemble script
   767  	if opts.Stdin != nil && (opts.Command == constants.Assemble || opts.Command == constants.Usage) {
   768  		untarAndRun := fmt.Sprintf("tar -C %s -xf - && %s", tarDestination, binaryToRun)
   769  
   770  		resultedCommand := untarAndRun
   771  		if opts.CommandOverrides != nil {
   772  			resultedCommand = opts.CommandOverrides(untarAndRun)
   773  		}
   774  		return []string{"/bin/sh", "-c", resultedCommand}
   775  	}
   776  
   777  	return []string{binaryToRun}
   778  }
   779  
   780  func determineTarDestinationDir(opts RunContainerOptions, imageMetadata *api.Image) string {
   781  	if len(opts.Destination) != 0 {
   782  		return opts.Destination
   783  	}
   784  	return getDestination(imageMetadata)
   785  }
   786  
   787  func determineCommandBaseDir(opts RunContainerOptions, imageMetadata *api.Image, tarDestination string) string {
   788  	if opts.ExternalScripts {
   789  		// for external scripts we must always append 'scripts' because this is
   790  		// the default subdirectory inside tar for them
   791  		// NOTE: We use path.Join instead of filepath.Join to avoid converting the
   792  		// path to UNC (Windows) format as we always run this inside container.
   793  		log.V(2).Infof("Both scripts and untarred source will be placed in '%s'", tarDestination)
   794  		return path.Join(tarDestination, "scripts")
   795  	}
   796  
   797  	// for internal scripts we can have separate path for scripts and untar operation destination
   798  	scriptsURL := opts.ScriptsURL
   799  	if len(scriptsURL) == 0 {
   800  		scriptsURL = getScriptsURL(imageMetadata)
   801  	}
   802  
   803  	commandBaseDir := strings.TrimPrefix(scriptsURL, "image://")
   804  	log.V(2).Infof("Base directory for S2I scripts is '%s'. Untarring destination is '%s'.",
   805  		commandBaseDir, tarDestination)
   806  
   807  	return commandBaseDir
   808  }
   809  
   810  // dumpContainerInfo dumps information about a running container (port/IP/etc).
   811  func dumpContainerInfo(container dockercontainer.ContainerCreateCreatedBody, d *stiDocker, image string) {
   812  	ctx, cancel := getDefaultContext()
   813  	defer cancel()
   814  
   815  	containerJSON, err := d.client.ContainerInspect(ctx, container.ID)
   816  	if err != nil {
   817  		return
   818  	}
   819  
   820  	liveports := "\n\nPort Bindings:  "
   821  	for port, bindings := range containerJSON.NetworkSettings.NetworkSettingsBase.Ports {
   822  		liveports = liveports + "\n  Container Port:  " + string(port)
   823  		liveports = liveports + "\n        Public Host / Port Mappings:"
   824  		for _, binding := range bindings {
   825  			liveports = liveports + "\n            IP: " + binding.HostIP + " Port: " + binding.HostPort
   826  		}
   827  	}
   828  	liveports = liveports + "\n"
   829  	log.V(0).Infof("\n\n\n\n\nThe image %s has been started in container %s as a result of the --run=true option.  The container's stdout/stderr will be redirected to this command's log output to help you validate its behavior.  You can also inspect the container with docker commands if you like.  If the container is set up to stay running, you will have to Ctrl-C to exit this command, which should also stop the container %s.  This particular invocation attempts to run with the port mappings %+v \n\n\n\n\n", image, container.ID, container.ID, liveports)
   830  }
   831  
   832  // redirectResponseToOutputStream handles incoming streamed data from a
   833  // container on a "hijacked" connection.  If tty is true, expect multiplexed
   834  // streams.  Rules: 1) if you ask for streamed data from a container, you have
   835  // to read it, otherwise sooner or later the container will block writing it.
   836  // 2) if you're receiving multiplexed data, you have to actively read both
   837  // streams in parallel, otherwise in the case of non-interleaved data, you, and
   838  // then the container, will block.
   839  func (d *stiDocker) redirectResponseToOutputStream(tty bool, outputStream, errorStream io.Writer, resp io.Reader) error {
   840  	if outputStream == nil {
   841  		outputStream = ioutil.Discard
   842  	}
   843  	if errorStream == nil {
   844  		errorStream = ioutil.Discard
   845  	}
   846  	var err error
   847  	if tty {
   848  		_, err = io.Copy(outputStream, resp)
   849  	} else {
   850  		_, err = dockerstdcopy.StdCopy(outputStream, errorStream, resp)
   851  	}
   852  	return err
   853  }
   854  
   855  // holdHijackedConnection pumps data up to the container's stdin, and runs a
   856  // goroutine to pump data down from the container's stdout and stderr.  it holds
   857  // open the HijackedResponse until all of this is done.  Caller's responsibility
   858  // to close resp, as well as outputStream and errorStream if appropriate.
   859  func (d *stiDocker) holdHijackedConnection(tty bool, opts *RunContainerOptions, resp dockertypes.HijackedResponse) error {
   860  	receiveStdout := make(chan error, 1)
   861  	if opts.Stdout != nil || opts.Stderr != nil {
   862  		go func() {
   863  			err := d.redirectResponseToOutputStream(tty, opts.Stdout, opts.Stderr, resp.Reader)
   864  			if opts.Stdout != nil {
   865  				opts.Stdout.Close()
   866  				opts.Stdout = nil
   867  			}
   868  			if opts.Stderr != nil {
   869  				opts.Stderr.Close()
   870  				opts.Stderr = nil
   871  			}
   872  			receiveStdout <- err
   873  		}()
   874  	} else {
   875  		receiveStdout <- nil
   876  	}
   877  
   878  	if opts.Stdin != nil {
   879  		_, err := io.Copy(resp.Conn, opts.Stdin)
   880  		opts.Stdin.Close()
   881  		opts.Stdin = nil
   882  		if err != nil {
   883  			<-receiveStdout
   884  			return err
   885  		}
   886  	}
   887  	err := resp.CloseWrite()
   888  	if err != nil {
   889  		<-receiveStdout
   890  		return err
   891  	}
   892  
   893  	// Hang around until the streaming is over - either when the server closes
   894  	// the connection, or someone locally closes resp.
   895  	return <-receiveStdout
   896  }
   897  
   898  // RunContainer creates and starts a container using the image specified in opts
   899  // with the ability to stream input and/or output.  Any non-nil
   900  // opts.Std{in,out,err} will be closed upon return.
   901  func (d *stiDocker) RunContainer(opts RunContainerOptions) error {
   902  	// Guarantee that Std{in,out,err} are closed upon return, including under
   903  	// error circumstances.  In normal circumstances, holdHijackedConnection
   904  	// should do this for us.
   905  	defer func() {
   906  		if opts.Stdin != nil {
   907  			opts.Stdin.Close()
   908  		}
   909  		if opts.Stdout != nil {
   910  			opts.Stdout.Close()
   911  		}
   912  		if opts.Stderr != nil {
   913  			opts.Stderr.Close()
   914  		}
   915  	}()
   916  
   917  	createOpts := opts.asDockerCreateContainerOptions()
   918  
   919  	// get info about the specified image
   920  	image := createOpts.Config.Image
   921  	inspect, err := d.InspectImage(image)
   922  	imageMetadata := &api.Image{}
   923  	if err == nil {
   924  		updateImageWithInspect(imageMetadata, inspect)
   925  		if opts.PullImage {
   926  			_, err = d.CheckAndPullImage(image)
   927  		}
   928  	}
   929  	if err != nil {
   930  		log.V(0).Infof("error: Unable to get image metadata for %s: %v", image, err)
   931  		return err
   932  	}
   933  
   934  	entrypoint, err := d.GetImageEntrypoint(image)
   935  	if err != nil {
   936  		return fmt.Errorf("could not  get entrypoint of %q image: %v", image, err)
   937  	}
   938  
   939  	// If the image has an entrypoint already defined,
   940  	// it will be overridden either by DefaultEntrypoint,
   941  	// or by the value in opts.Entrypoint.
   942  	// If the image does not have an entrypoint, but
   943  	// opts.Entrypoint is supplied, opts.Entrypoint will
   944  	// be respected.
   945  	if len(entrypoint) != 0 && len(opts.Entrypoint) == 0 {
   946  		opts.Entrypoint = DefaultEntrypoint
   947  	}
   948  
   949  	// tarDestination will be passed as location to PostExecute function
   950  	// and will be used as the prefix for the CMD (scripts/run)
   951  	var tarDestination string
   952  
   953  	var cmd []string
   954  	if !opts.TargetImage {
   955  		if len(opts.CommandExplicit) != 0 {
   956  			cmd = opts.CommandExplicit
   957  		} else {
   958  			tarDestination = determineTarDestinationDir(opts, imageMetadata)
   959  			cmd = constructCommand(opts, imageMetadata, tarDestination)
   960  		}
   961  		log.V(5).Infof("Setting %q command for container ...", strings.Join(cmd, " "))
   962  	}
   963  	createOpts.Config.Cmd = cmd
   964  
   965  	if createOpts.HostConfig != nil && createOpts.HostConfig.ShmSize <= 0 {
   966  		createOpts.HostConfig.ShmSize = DefaultShmSize
   967  	}
   968  
   969  	// Create a new container.
   970  	log.V(2).Infof("Creating container with options {Name:%q Config:%+v HostConfig:%+v} ...", createOpts.Name, *util.SafeForLoggingContainerConfig(createOpts.Config), createOpts.HostConfig)
   971  	ctx, cancel := getDefaultContext()
   972  	defer cancel()
   973  	container, err := d.client.ContainerCreate(ctx, createOpts.Config, createOpts.HostConfig, createOpts.NetworkingConfig, createOpts.Name)
   974  	if err != nil {
   975  		return err
   976  	}
   977  
   978  	// Container was created, so we defer its removal, and also remove it if we get a SIGINT/SIGTERM/SIGQUIT/SIGHUP.
   979  	removeContainer := func() {
   980  		log.V(4).Infof("Removing container %q ...", container.ID)
   981  
   982  		killErr := d.KillContainer(container.ID)
   983  
   984  		if removeErr := d.RemoveContainer(container.ID); removeErr != nil {
   985  			if killErr != nil {
   986  				log.V(0).Infof("warning: Failed to kill container %q: %v", container.ID, killErr)
   987  			}
   988  			log.V(0).Infof("warning: Failed to remove container %q: %v", container.ID, removeErr)
   989  		} else {
   990  			log.V(4).Infof("Removed container %q", container.ID)
   991  		}
   992  	}
   993  	dumpStack := func(signal os.Signal) {
   994  		if signal == syscall.SIGQUIT {
   995  			buf := make([]byte, 1<<16)
   996  			runtime.Stack(buf, true)
   997  			fmt.Printf("%s", buf)
   998  		}
   999  		os.Exit(2)
  1000  	}
  1001  	return interrupt.New(dumpStack, removeContainer).Run(func() error {
  1002  		log.V(2).Infof("Attaching to container %q ...", container.ID)
  1003  		ctx, cancel := getDefaultContext()
  1004  		defer cancel()
  1005  		resp, err := d.client.ContainerAttach(ctx, container.ID, opts.asDockerAttachToContainerOptions())
  1006  		if err != nil {
  1007  			log.V(0).Infof("error: Unable to attach to container %q: %v", container.ID, err)
  1008  			return err
  1009  		}
  1010  		defer resp.Close()
  1011  
  1012  		// Start the container
  1013  		log.V(2).Infof("Starting container %q ...", container.ID)
  1014  		ctx, cancel = getDefaultContext()
  1015  		defer cancel()
  1016  		err = d.client.ContainerStart(ctx, container.ID, dockertypes.ContainerStartOptions{})
  1017  		if err != nil {
  1018  			return err
  1019  		}
  1020  
  1021  		// Run OnStart hook if defined. OnStart might block, so we run it in a
  1022  		// new goroutine, and wait for it to be done later on.
  1023  		onStartDone := make(chan error, 1)
  1024  		if opts.OnStart != nil {
  1025  			go func() {
  1026  				onStartDone <- opts.OnStart(container.ID)
  1027  			}()
  1028  		}
  1029  
  1030  		if opts.TargetImage {
  1031  			// When TargetImage is true, we're dealing with an invocation of `s2i build ... --run`
  1032  			// so this will, e.g., run a web server and block until the user interrupts it (or
  1033  			// the container exits normally).  dump port/etc information for the user.
  1034  			dumpContainerInfo(container, d, image)
  1035  		}
  1036  
  1037  		err = d.holdHijackedConnection(false, &opts, resp)
  1038  		if err != nil {
  1039  			return err
  1040  		}
  1041  
  1042  		// Return an error if the exit code of the container is
  1043  		// non-zero.
  1044  		log.V(4).Infof("Waiting for container %q to stop ...", container.ID)
  1045  		waitC, errC := d.client.ContainerWait(context.Background(), container.ID, dockercontainer.WaitConditionNotRunning)
  1046  		select {
  1047  		case result := <-waitC:
  1048  			if result.StatusCode != 0 {
  1049  				var output string
  1050  				jsonOutput, _ := d.client.ContainerInspect(ctx, container.ID)
  1051  				if err == nil && jsonOutput.ContainerJSONBase != nil && jsonOutput.ContainerJSONBase.State != nil {
  1052  					state := jsonOutput.ContainerJSONBase.State
  1053  					output = fmt.Sprintf("Status: %s, Error: %s, OOMKilled: %v, Dead: %v", state.Status, state.Error, state.OOMKilled, state.Dead)
  1054  				}
  1055  				return s2ierr.NewContainerError(container.ID, int(result.StatusCode), output)
  1056  			}
  1057  		case err := <-errC:
  1058  			return fmt.Errorf("waiting for container %q to stop: %v", container.ID, err)
  1059  		}
  1060  
  1061  		// OnStart must be done before we move on.
  1062  		if opts.OnStart != nil {
  1063  			if err = <-onStartDone; err != nil {
  1064  				return err
  1065  			}
  1066  		}
  1067  		// Run PostExec hook if defined.
  1068  		if opts.PostExec != nil {
  1069  			log.V(2).Infof("Invoking PostExecute function")
  1070  			if err = opts.PostExec.PostExecute(container.ID, tarDestination); err != nil {
  1071  				return err
  1072  			}
  1073  		}
  1074  		return nil
  1075  	})
  1076  }
  1077  
  1078  // GetImageID retrieves the ID of the image identified by name
  1079  func (d *stiDocker) GetImageID(name string) (string, error) {
  1080  	name = getImageName(name)
  1081  	image, err := d.InspectImage(name)
  1082  	if err != nil {
  1083  		return "", err
  1084  	}
  1085  	return image.ID, nil
  1086  }
  1087  
  1088  // CommitContainer commits a container to an image with a specific tag.
  1089  // The new image ID is returned
  1090  func (d *stiDocker) CommitContainer(opts CommitContainerOptions) (string, error) {
  1091  	dockerOpts := dockertypes.ContainerCommitOptions{
  1092  		Reference: opts.Repository,
  1093  	}
  1094  	if opts.Command != nil || opts.Entrypoint != nil {
  1095  		config := dockercontainer.Config{
  1096  			Cmd:        opts.Command,
  1097  			Entrypoint: opts.Entrypoint,
  1098  			Env:        opts.Env,
  1099  			Labels:     opts.Labels,
  1100  			User:       opts.User,
  1101  		}
  1102  		dockerOpts.Config = &config
  1103  		log.V(2).Infof("Committing container with dockerOpts: %+v, config: %+v", dockerOpts, *util.SafeForLoggingContainerConfig(&config))
  1104  	}
  1105  
  1106  	resp, err := d.client.ContainerCommit(context.Background(), opts.ContainerID, dockerOpts)
  1107  	if err == nil {
  1108  		return resp.ID, nil
  1109  	}
  1110  	return "", err
  1111  }
  1112  
  1113  // RemoveImage removes the image with specified ID
  1114  func (d *stiDocker) RemoveImage(imageID string) error {
  1115  	ctx, cancel := getDefaultContext()
  1116  	defer cancel()
  1117  	_, err := d.client.ImageRemove(ctx, imageID, dockertypes.ImageRemoveOptions{})
  1118  	return err
  1119  }
  1120  
  1121  // BuildImage builds the image according to specified options
  1122  func (d *stiDocker) BuildImage(opts BuildImageOptions) error {
  1123  	dockerOpts := dockertypes.ImageBuildOptions{
  1124  		Tags:           []string{opts.Name},
  1125  		NoCache:        true,
  1126  		SuppressOutput: false,
  1127  		Remove:         true,
  1128  		ForceRemove:    true,
  1129  	}
  1130  	if opts.CGroupLimits != nil {
  1131  		dockerOpts.Memory = opts.CGroupLimits.MemoryLimitBytes
  1132  		dockerOpts.MemorySwap = opts.CGroupLimits.MemorySwap
  1133  		dockerOpts.CgroupParent = opts.CGroupLimits.Parent
  1134  	}
  1135  	log.V(2).Infof("Building container using config: %+v", dockerOpts)
  1136  	resp, err := d.client.ImageBuild(context.Background(), opts.Stdin, dockerOpts)
  1137  	if err != nil {
  1138  		return err
  1139  	}
  1140  	defer resp.Body.Close()
  1141  	// since can't pass in output stream to engine-api, need to copy contents of
  1142  	// the output stream they create into our output stream
  1143  	_, err = io.Copy(opts.Stdout, resp.Body)
  1144  	if opts.Stdout != nil {
  1145  		opts.Stdout.Close()
  1146  	}
  1147  	return err
  1148  }