github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/builder/dockerfile/internals.go (about)

     1  package dockerfile // import "github.com/docker/docker/builder/dockerfile"
     2  
     3  // internals for handling commands. Covers many areas and a lot of
     4  // non-contiguous functionality. Please read the comments.
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  
    17  	"github.com/docker/docker/api/types"
    18  	"github.com/docker/docker/api/types/backend"
    19  	"github.com/docker/docker/api/types/container"
    20  	"github.com/docker/docker/builder"
    21  	"github.com/docker/docker/image"
    22  	"github.com/docker/docker/pkg/archive"
    23  	"github.com/docker/docker/pkg/chrootarchive"
    24  	"github.com/docker/docker/pkg/containerfs"
    25  	"github.com/docker/docker/pkg/idtools"
    26  	"github.com/docker/docker/pkg/stringid"
    27  	"github.com/docker/docker/pkg/system"
    28  	"github.com/docker/go-connections/nat"
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  // Archiver defines an interface for copying files from one destination to
    33  // another using Tar/Untar.
    34  type Archiver interface {
    35  	TarUntar(src, dst string) error
    36  	UntarPath(src, dst string) error
    37  	CopyWithTar(src, dst string) error
    38  	CopyFileWithTar(src, dst string) error
    39  	IDMappings() *idtools.IDMappings
    40  }
    41  
    42  // The builder will use the following interfaces if the container fs implements
    43  // these for optimized copies to and from the container.
    44  type extractor interface {
    45  	ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error
    46  }
    47  
    48  type archiver interface {
    49  	ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error)
    50  }
    51  
    52  // helper functions to get tar/untar func
    53  func untarFunc(i interface{}) containerfs.UntarFunc {
    54  	if ea, ok := i.(extractor); ok {
    55  		return ea.ExtractArchive
    56  	}
    57  	return chrootarchive.Untar
    58  }
    59  
    60  func tarFunc(i interface{}) containerfs.TarFunc {
    61  	if ap, ok := i.(archiver); ok {
    62  		return ap.ArchivePath
    63  	}
    64  	return archive.TarWithOptions
    65  }
    66  
    67  func (b *Builder) getArchiver(src, dst containerfs.Driver) Archiver {
    68  	t, u := tarFunc(src), untarFunc(dst)
    69  	return &containerfs.Archiver{
    70  		SrcDriver:     src,
    71  		DstDriver:     dst,
    72  		Tar:           t,
    73  		Untar:         u,
    74  		IDMappingsVar: b.idMappings,
    75  	}
    76  }
    77  
    78  func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
    79  	if b.disableCommit {
    80  		return nil
    81  	}
    82  	if !dispatchState.hasFromImage() {
    83  		return errors.New("Please provide a source image with `from` prior to commit")
    84  	}
    85  
    86  	optionsPlatform := system.ParsePlatform(b.options.Platform)
    87  	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, optionsPlatform.OS))
    88  	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
    89  	if err != nil || hit {
    90  		return err
    91  	}
    92  	id, err := b.create(runConfigWithCommentCmd)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	return b.commitContainer(dispatchState, id, runConfigWithCommentCmd)
    98  }
    99  
   100  func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
   101  	if b.disableCommit {
   102  		return nil
   103  	}
   104  
   105  	commitCfg := backend.CommitConfig{
   106  		Author: dispatchState.maintainer,
   107  		// TODO: this copy should be done by Commit()
   108  		Config:          copyRunConfig(dispatchState.runConfig),
   109  		ContainerConfig: containerConfig,
   110  		ContainerID:     id,
   111  	}
   112  
   113  	imageID, err := b.docker.CommitBuildStep(commitCfg)
   114  	dispatchState.imageID = string(imageID)
   115  	return err
   116  }
   117  
   118  func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error {
   119  	newLayer, err := layer.Commit()
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	// add an image mount without an image so the layer is properly unmounted
   125  	// if there is an error before we can add the full mount with image
   126  	b.imageSources.Add(newImageMount(nil, newLayer))
   127  
   128  	parentImage, ok := parent.(*image.Image)
   129  	if !ok {
   130  		return errors.Errorf("unexpected image type")
   131  	}
   132  
   133  	newImage := image.NewChildImage(parentImage, image.ChildConfig{
   134  		Author:          state.maintainer,
   135  		ContainerConfig: runConfig,
   136  		DiffID:          newLayer.DiffID(),
   137  		Config:          copyRunConfig(state.runConfig),
   138  	}, parentImage.OS)
   139  
   140  	// TODO: it seems strange to marshal this here instead of just passing in the
   141  	// image struct
   142  	config, err := newImage.MarshalJSON()
   143  	if err != nil {
   144  		return errors.Wrap(err, "failed to encode image config")
   145  	}
   146  
   147  	exportedImage, err := b.docker.CreateImage(config, state.imageID)
   148  	if err != nil {
   149  		return errors.Wrapf(err, "failed to export image")
   150  	}
   151  
   152  	state.imageID = exportedImage.ImageID()
   153  	b.imageSources.Add(newImageMount(exportedImage, newLayer))
   154  	return nil
   155  }
   156  
   157  func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error {
   158  	srcHash := getSourceHashFromInfos(inst.infos)
   159  
   160  	var chownComment string
   161  	if inst.chownStr != "" {
   162  		chownComment = fmt.Sprintf("--chown=%s", inst.chownStr)
   163  	}
   164  	commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest)
   165  
   166  	// TODO: should this have been using origPaths instead of srcHash in the comment?
   167  	optionsPlatform := system.ParsePlatform(b.options.Platform)
   168  	runConfigWithCommentCmd := copyRunConfig(
   169  		state.runConfig,
   170  		withCmdCommentString(commentStr, optionsPlatform.OS))
   171  	hit, err := b.probeCache(state, runConfigWithCommentCmd)
   172  	if err != nil || hit {
   173  		return err
   174  	}
   175  
   176  	imageMount, err := b.imageSources.Get(state.imageID, true)
   177  	if err != nil {
   178  		return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
   179  	}
   180  
   181  	rwLayer, err := imageMount.NewRWLayer()
   182  	if err != nil {
   183  		return err
   184  	}
   185  	defer rwLayer.Release()
   186  
   187  	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, b.options.Platform)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	chownPair := b.idMappings.RootPair()
   193  	// if a chown was requested, perform the steps to get the uid, gid
   194  	// translated (if necessary because of user namespaces), and replace
   195  	// the root pair with the chown pair for copy operations
   196  	if inst.chownStr != "" {
   197  		chownPair, err = parseChownFlag(inst.chownStr, destInfo.root.Path(), b.idMappings)
   198  		if err != nil {
   199  			return errors.Wrapf(err, "unable to convert uid/gid chown string to host mapping")
   200  		}
   201  	}
   202  
   203  	for _, info := range inst.infos {
   204  		opts := copyFileOptions{
   205  			decompress: inst.allowLocalDecompression,
   206  			archiver:   b.getArchiver(info.root, destInfo.root),
   207  			chownPair:  chownPair,
   208  		}
   209  		if err := performCopyForInfo(destInfo, info, opts); err != nil {
   210  			return errors.Wrapf(err, "failed to copy files")
   211  		}
   212  	}
   213  	return b.exportImage(state, rwLayer, imageMount.Image(), runConfigWithCommentCmd)
   214  }
   215  
   216  func createDestInfo(workingDir string, inst copyInstruction, rwLayer builder.RWLayer, platform string) (copyInfo, error) {
   217  	// Twiddle the destination when it's a relative path - meaning, make it
   218  	// relative to the WORKINGDIR
   219  	dest, err := normalizeDest(workingDir, inst.dest, platform)
   220  	if err != nil {
   221  		return copyInfo{}, errors.Wrapf(err, "invalid %s", inst.cmdName)
   222  	}
   223  
   224  	return copyInfo{root: rwLayer.Root(), path: dest}, nil
   225  }
   226  
   227  // normalizeDest normalises the destination of a COPY/ADD command in a
   228  // platform semantically consistent way.
   229  func normalizeDest(workingDir, requested string, platform string) (string, error) {
   230  	dest := fromSlash(requested, platform)
   231  	endsInSlash := strings.HasSuffix(dest, string(separator(platform)))
   232  
   233  	if platform != "windows" {
   234  		if !path.IsAbs(requested) {
   235  			dest = path.Join("/", filepath.ToSlash(workingDir), dest)
   236  			// Make sure we preserve any trailing slash
   237  			if endsInSlash {
   238  				dest += "/"
   239  			}
   240  		}
   241  		return dest, nil
   242  	}
   243  
   244  	// We are guaranteed that the working directory is already consistent,
   245  	// However, Windows also has, for now, the limitation that ADD/COPY can
   246  	// only be done to the system drive, not any drives that might be present
   247  	// as a result of a bind mount.
   248  	//
   249  	// So... if the path requested is Linux-style absolute (/foo or \\foo),
   250  	// we assume it is the system drive. If it is a Windows-style absolute
   251  	// (DRIVE:\\foo), error if DRIVE is not C. And finally, ensure we
   252  	// strip any configured working directories drive letter so that it
   253  	// can be subsequently legitimately converted to a Windows volume-style
   254  	// pathname.
   255  
   256  	// Not a typo - filepath.IsAbs, not system.IsAbs on this next check as
   257  	// we only want to validate where the DriveColon part has been supplied.
   258  	if filepath.IsAbs(dest) {
   259  		if strings.ToUpper(string(dest[0])) != "C" {
   260  			return "", fmt.Errorf("Windows does not support destinations not on the system drive (C:)")
   261  		}
   262  		dest = dest[2:] // Strip the drive letter
   263  	}
   264  
   265  	// Cannot handle relative where WorkingDir is not the system drive.
   266  	if len(workingDir) > 0 {
   267  		if ((len(workingDir) > 1) && !system.IsAbs(workingDir[2:])) || (len(workingDir) == 1) {
   268  			return "", fmt.Errorf("Current WorkingDir %s is not platform consistent", workingDir)
   269  		}
   270  		if !system.IsAbs(dest) {
   271  			if string(workingDir[0]) != "C" {
   272  				return "", fmt.Errorf("Windows does not support relative paths when WORKDIR is not the system drive")
   273  			}
   274  			dest = filepath.Join(string(os.PathSeparator), workingDir[2:], dest)
   275  			// Make sure we preserve any trailing slash
   276  			if endsInSlash {
   277  				dest += string(os.PathSeparator)
   278  			}
   279  		}
   280  	}
   281  	return dest, nil
   282  }
   283  
   284  // For backwards compat, if there's just one info then use it as the
   285  // cache look-up string, otherwise hash 'em all into one
   286  func getSourceHashFromInfos(infos []copyInfo) string {
   287  	if len(infos) == 1 {
   288  		return infos[0].hash
   289  	}
   290  	var hashs []string
   291  	for _, info := range infos {
   292  		hashs = append(hashs, info.hash)
   293  	}
   294  	return hashStringSlice("multi", hashs)
   295  }
   296  
   297  func hashStringSlice(prefix string, slice []string) string {
   298  	hasher := sha256.New()
   299  	hasher.Write([]byte(strings.Join(slice, ",")))
   300  	return prefix + ":" + hex.EncodeToString(hasher.Sum(nil))
   301  }
   302  
   303  type runConfigModifier func(*container.Config)
   304  
   305  func withCmd(cmd []string) runConfigModifier {
   306  	return func(runConfig *container.Config) {
   307  		runConfig.Cmd = cmd
   308  	}
   309  }
   310  
   311  // withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
   312  // why there are two almost identical versions of this.
   313  func withCmdComment(comment string, platform string) runConfigModifier {
   314  	return func(runConfig *container.Config) {
   315  		runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) ", comment)
   316  	}
   317  }
   318  
   319  // withCmdCommentString exists to maintain compatibility with older versions.
   320  // A few instructions (workdir, copy, add) used a nop comment that is a single arg
   321  // where as all the other instructions used a two arg comment string. This
   322  // function implements the single arg version.
   323  func withCmdCommentString(comment string, platform string) runConfigModifier {
   324  	return func(runConfig *container.Config) {
   325  		runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) "+comment)
   326  	}
   327  }
   328  
   329  func withEnv(env []string) runConfigModifier {
   330  	return func(runConfig *container.Config) {
   331  		runConfig.Env = env
   332  	}
   333  }
   334  
   335  // withEntrypointOverride sets an entrypoint on runConfig if the command is
   336  // not empty. The entrypoint is left unmodified if command is empty.
   337  //
   338  // The dockerfile RUN instruction expect to run without an entrypoint
   339  // so the runConfig entrypoint needs to be modified accordingly. ContainerCreate
   340  // will change a []string{""} entrypoint to nil, so we probe the cache with the
   341  // nil entrypoint.
   342  func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier {
   343  	return func(runConfig *container.Config) {
   344  		if len(cmd) > 0 {
   345  			runConfig.Entrypoint = entrypoint
   346  		}
   347  	}
   348  }
   349  
   350  func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config {
   351  	copy := *runConfig
   352  	copy.Cmd = copyStringSlice(runConfig.Cmd)
   353  	copy.Env = copyStringSlice(runConfig.Env)
   354  	copy.Entrypoint = copyStringSlice(runConfig.Entrypoint)
   355  	copy.OnBuild = copyStringSlice(runConfig.OnBuild)
   356  	copy.Shell = copyStringSlice(runConfig.Shell)
   357  
   358  	if copy.Volumes != nil {
   359  		copy.Volumes = make(map[string]struct{}, len(runConfig.Volumes))
   360  		for k, v := range runConfig.Volumes {
   361  			copy.Volumes[k] = v
   362  		}
   363  	}
   364  
   365  	if copy.ExposedPorts != nil {
   366  		copy.ExposedPorts = make(nat.PortSet, len(runConfig.ExposedPorts))
   367  		for k, v := range runConfig.ExposedPorts {
   368  			copy.ExposedPorts[k] = v
   369  		}
   370  	}
   371  
   372  	if copy.Labels != nil {
   373  		copy.Labels = make(map[string]string, len(runConfig.Labels))
   374  		for k, v := range runConfig.Labels {
   375  			copy.Labels[k] = v
   376  		}
   377  	}
   378  
   379  	for _, modifier := range modifiers {
   380  		modifier(&copy)
   381  	}
   382  	return &copy
   383  }
   384  
   385  func copyStringSlice(orig []string) []string {
   386  	if orig == nil {
   387  		return nil
   388  	}
   389  	return append([]string{}, orig...)
   390  }
   391  
   392  // getShell is a helper function which gets the right shell for prefixing the
   393  // shell-form of RUN, ENTRYPOINT and CMD instructions
   394  func getShell(c *container.Config, os string) []string {
   395  	if 0 == len(c.Shell) {
   396  		return append([]string{}, defaultShellForOS(os)[:]...)
   397  	}
   398  	return append([]string{}, c.Shell[:]...)
   399  }
   400  
   401  func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
   402  	cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig)
   403  	if cachedID == "" || err != nil {
   404  		return false, err
   405  	}
   406  	fmt.Fprint(b.Stdout, " ---> Using cache\n")
   407  
   408  	dispatchState.imageID = cachedID
   409  	return true, nil
   410  }
   411  
   412  var defaultLogConfig = container.LogConfig{Type: "none"}
   413  
   414  func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *container.Config) (string, error) {
   415  	if hit, err := b.probeCache(dispatchState, runConfig); err != nil || hit {
   416  		return "", err
   417  	}
   418  	// Set a log config to override any default value set on the daemon
   419  	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
   420  	container, err := b.containerManager.Create(runConfig, hostConfig)
   421  	return container.ID, err
   422  }
   423  
   424  func (b *Builder) create(runConfig *container.Config) (string, error) {
   425  	hostConfig := hostConfigFromOptions(b.options)
   426  	container, err := b.containerManager.Create(runConfig, hostConfig)
   427  	if err != nil {
   428  		return "", err
   429  	}
   430  	// TODO: could this be moved into containerManager.Create() ?
   431  	for _, warning := range container.Warnings {
   432  		fmt.Fprintf(b.Stdout, " ---> [Warning] %s\n", warning)
   433  	}
   434  	fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(container.ID))
   435  	return container.ID, nil
   436  }
   437  
   438  func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConfig {
   439  	resources := container.Resources{
   440  		CgroupParent: options.CgroupParent,
   441  		CPUShares:    options.CPUShares,
   442  		CPUPeriod:    options.CPUPeriod,
   443  		CPUQuota:     options.CPUQuota,
   444  		CpusetCpus:   options.CPUSetCPUs,
   445  		CpusetMems:   options.CPUSetMems,
   446  		Memory:       options.Memory,
   447  		MemorySwap:   options.MemorySwap,
   448  		Ulimits:      options.Ulimits,
   449  	}
   450  
   451  	hc := &container.HostConfig{
   452  		SecurityOpt: options.SecurityOpt,
   453  		Isolation:   options.Isolation,
   454  		ShmSize:     options.ShmSize,
   455  		Resources:   resources,
   456  		NetworkMode: container.NetworkMode(options.NetworkMode),
   457  		// Set a log config to override any default value set on the daemon
   458  		LogConfig:  defaultLogConfig,
   459  		ExtraHosts: options.ExtraHosts,
   460  	}
   461  
   462  	// For WCOW, the default of 20GB hard-coded in the platform
   463  	// is too small for builder scenarios where many users are
   464  	// using RUN statements to install large amounts of data.
   465  	// Use 127GB as that's the default size of a VHD in Hyper-V.
   466  	if runtime.GOOS == "windows" && options.Platform == "windows" {
   467  		hc.StorageOpt = make(map[string]string)
   468  		hc.StorageOpt["size"] = "127GB"
   469  	}
   470  
   471  	return hc
   472  }
   473  
   474  // fromSlash works like filepath.FromSlash but with a given OS platform field
   475  func fromSlash(path, platform string) string {
   476  	if platform == "windows" {
   477  		return strings.Replace(path, "/", "\\", -1)
   478  	}
   479  	return path
   480  }
   481  
   482  // separator returns a OS path separator for the given OS platform
   483  func separator(platform string) byte {
   484  	if platform == "windows" {
   485  		return '\\'
   486  	}
   487  	return '/'
   488  }