github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/builder/dockerfile/internals.go (about)

     1  package 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  	"io/ioutil"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"path/filepath"
    16  	"runtime"
    17  	"sort"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/Sirupsen/logrus"
    22  	"github.com/docker/docker/builder"
    23  	"github.com/docker/docker/builder/dockerfile/parser"
    24  	"github.com/docker/docker/pkg/archive"
    25  	"github.com/docker/docker/pkg/httputils"
    26  	"github.com/docker/docker/pkg/ioutils"
    27  	"github.com/docker/docker/pkg/jsonmessage"
    28  	"github.com/docker/docker/pkg/progress"
    29  	"github.com/docker/docker/pkg/streamformatter"
    30  	"github.com/docker/docker/pkg/stringid"
    31  	"github.com/docker/docker/pkg/system"
    32  	"github.com/docker/docker/pkg/tarsum"
    33  	"github.com/docker/docker/pkg/urlutil"
    34  	"github.com/docker/docker/runconfig/opts"
    35  	"github.com/docker/engine-api/types"
    36  	"github.com/docker/engine-api/types/container"
    37  	"github.com/docker/engine-api/types/strslice"
    38  )
    39  
    40  func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) error {
    41  	if b.disableCommit {
    42  		return nil
    43  	}
    44  	if b.image == "" && !b.noBaseImage {
    45  		return fmt.Errorf("Please provide a source image with `from` prior to commit")
    46  	}
    47  	b.runConfig.Image = b.image
    48  	if id == "" {
    49  		cmd := b.runConfig.Cmd
    50  		if runtime.GOOS != "windows" {
    51  			b.runConfig.Cmd = strslice.StrSlice{"/bin/sh", "-c", "#(nop) " + comment}
    52  		} else {
    53  			b.runConfig.Cmd = strslice.StrSlice{"cmd", "/S /C", "REM (nop) " + comment}
    54  		}
    55  		defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
    56  
    57  		hit, err := b.probeCache()
    58  		if err != nil {
    59  			return err
    60  		} else if hit {
    61  			return nil
    62  		}
    63  		id, err = b.create()
    64  		if err != nil {
    65  			return err
    66  		}
    67  	}
    68  
    69  	// Note: Actually copy the struct
    70  	autoConfig := *b.runConfig
    71  	autoConfig.Cmd = autoCmd
    72  
    73  	commitCfg := &types.ContainerCommitConfig{
    74  		Author: b.maintainer,
    75  		Pause:  true,
    76  		Config: &autoConfig,
    77  	}
    78  
    79  	// Commit the container
    80  	imageID, err := b.docker.Commit(id, commitCfg)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	b.image = imageID
    85  	return nil
    86  }
    87  
    88  type copyInfo struct {
    89  	builder.FileInfo
    90  	decompress bool
    91  }
    92  
    93  func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string) error {
    94  	if b.context == nil {
    95  		return fmt.Errorf("No context given. Impossible to use %s", cmdName)
    96  	}
    97  
    98  	if len(args) < 2 {
    99  		return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
   100  	}
   101  
   102  	// Work in daemon-specific filepath semantics
   103  	dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest
   104  
   105  	b.runConfig.Image = b.image
   106  
   107  	var infos []copyInfo
   108  
   109  	// Loop through each src file and calculate the info we need to
   110  	// do the copy (e.g. hash value if cached).  Don't actually do
   111  	// the copy until we've looked at all src files
   112  	var err error
   113  	for _, orig := range args[0 : len(args)-1] {
   114  		var fi builder.FileInfo
   115  		decompress := allowLocalDecompression
   116  		if urlutil.IsURL(orig) {
   117  			if !allowRemote {
   118  				return fmt.Errorf("Source can't be a URL for %s", cmdName)
   119  			}
   120  			fi, err = b.download(orig)
   121  			if err != nil {
   122  				return err
   123  			}
   124  			defer os.RemoveAll(filepath.Dir(fi.Path()))
   125  			decompress = false
   126  			infos = append(infos, copyInfo{fi, decompress})
   127  			continue
   128  		}
   129  		// not a URL
   130  		subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true)
   131  		if err != nil {
   132  			return err
   133  		}
   134  
   135  		infos = append(infos, subInfos...)
   136  	}
   137  
   138  	if len(infos) == 0 {
   139  		return fmt.Errorf("No source files were specified")
   140  	}
   141  	if len(infos) > 1 && !strings.HasSuffix(dest, string(os.PathSeparator)) {
   142  		return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
   143  	}
   144  
   145  	// For backwards compat, if there's just one info then use it as the
   146  	// cache look-up string, otherwise hash 'em all into one
   147  	var srcHash string
   148  	var origPaths string
   149  
   150  	if len(infos) == 1 {
   151  		fi := infos[0].FileInfo
   152  		origPaths = fi.Name()
   153  		if hfi, ok := fi.(builder.Hashed); ok {
   154  			srcHash = hfi.Hash()
   155  		}
   156  	} else {
   157  		var hashs []string
   158  		var origs []string
   159  		for _, info := range infos {
   160  			fi := info.FileInfo
   161  			origs = append(origs, fi.Name())
   162  			if hfi, ok := fi.(builder.Hashed); ok {
   163  				hashs = append(hashs, hfi.Hash())
   164  			}
   165  		}
   166  		hasher := sha256.New()
   167  		hasher.Write([]byte(strings.Join(hashs, ",")))
   168  		srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil))
   169  		origPaths = strings.Join(origs, " ")
   170  	}
   171  
   172  	cmd := b.runConfig.Cmd
   173  	if runtime.GOOS != "windows" {
   174  		b.runConfig.Cmd = strslice.StrSlice{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)}
   175  	} else {
   176  		b.runConfig.Cmd = strslice.StrSlice{"cmd", "/S", "/C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest)}
   177  	}
   178  	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
   179  
   180  	if hit, err := b.probeCache(); err != nil {
   181  		return err
   182  	} else if hit {
   183  		return nil
   184  	}
   185  
   186  	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig})
   187  	if err != nil {
   188  		return err
   189  	}
   190  	b.tmpContainers[container.ID] = struct{}{}
   191  
   192  	comment := fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)
   193  
   194  	// Twiddle the destination when its a relative path - meaning, make it
   195  	// relative to the WORKINGDIR
   196  	if !system.IsAbs(dest) {
   197  		hasSlash := strings.HasSuffix(dest, string(os.PathSeparator))
   198  		dest = filepath.Join(string(os.PathSeparator), filepath.FromSlash(b.runConfig.WorkingDir), dest)
   199  
   200  		// Make sure we preserve any trailing slash
   201  		if hasSlash {
   202  			dest += string(os.PathSeparator)
   203  		}
   204  	}
   205  
   206  	for _, info := range infos {
   207  		if err := b.docker.CopyOnBuild(container.ID, dest, info.FileInfo, info.decompress); err != nil {
   208  			return err
   209  		}
   210  	}
   211  
   212  	return b.commit(container.ID, cmd, comment)
   213  }
   214  
   215  func (b *Builder) download(srcURL string) (fi builder.FileInfo, err error) {
   216  	// get filename from URL
   217  	u, err := url.Parse(srcURL)
   218  	if err != nil {
   219  		return
   220  	}
   221  	path := filepath.FromSlash(u.Path) // Ensure in platform semantics
   222  	if strings.HasSuffix(path, string(os.PathSeparator)) {
   223  		path = path[:len(path)-1]
   224  	}
   225  	parts := strings.Split(path, string(os.PathSeparator))
   226  	filename := parts[len(parts)-1]
   227  	if filename == "" {
   228  		err = fmt.Errorf("cannot determine filename from url: %s", u)
   229  		return
   230  	}
   231  
   232  	// Initiate the download
   233  	resp, err := httputils.Download(srcURL)
   234  	if err != nil {
   235  		return
   236  	}
   237  
   238  	// Prepare file in a tmp dir
   239  	tmpDir, err := ioutils.TempDir("", "docker-remote")
   240  	if err != nil {
   241  		return
   242  	}
   243  	defer func() {
   244  		if err != nil {
   245  			os.RemoveAll(tmpDir)
   246  		}
   247  	}()
   248  	tmpFileName := filepath.Join(tmpDir, filename)
   249  	tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
   250  	if err != nil {
   251  		return
   252  	}
   253  
   254  	stdoutFormatter := b.Stdout.(*streamformatter.StdoutFormatter)
   255  	progressOutput := stdoutFormatter.StreamFormatter.NewProgressOutput(stdoutFormatter.Writer, true)
   256  	progressReader := progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Downloading")
   257  	// Download and dump result to tmp file
   258  	if _, err = io.Copy(tmpFile, progressReader); err != nil {
   259  		tmpFile.Close()
   260  		return
   261  	}
   262  	fmt.Fprintln(b.Stdout)
   263  	// ignoring error because the file was already opened successfully
   264  	tmpFileSt, err := tmpFile.Stat()
   265  	if err != nil {
   266  		return
   267  	}
   268  	tmpFile.Close()
   269  
   270  	// Set the mtime to the Last-Modified header value if present
   271  	// Otherwise just remove atime and mtime
   272  	mTime := time.Time{}
   273  
   274  	lastMod := resp.Header.Get("Last-Modified")
   275  	if lastMod != "" {
   276  		// If we can't parse it then just let it default to 'zero'
   277  		// otherwise use the parsed time value
   278  		if parsedMTime, err := http.ParseTime(lastMod); err == nil {
   279  			mTime = parsedMTime
   280  		}
   281  	}
   282  
   283  	if err = system.Chtimes(tmpFileName, mTime, mTime); err != nil {
   284  		return
   285  	}
   286  
   287  	// Calc the checksum, even if we're using the cache
   288  	r, err := archive.Tar(tmpFileName, archive.Uncompressed)
   289  	if err != nil {
   290  		return
   291  	}
   292  	tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version1)
   293  	if err != nil {
   294  		return
   295  	}
   296  	if _, err = io.Copy(ioutil.Discard, tarSum); err != nil {
   297  		return
   298  	}
   299  	hash := tarSum.Sum(nil)
   300  	r.Close()
   301  	return &builder.HashedFileInfo{FileInfo: builder.PathFileInfo{FileInfo: tmpFileSt, FilePath: tmpFileName}, FileHash: hash}, nil
   302  }
   303  
   304  func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool) ([]copyInfo, error) {
   305  
   306  	// Work in daemon-specific OS filepath semantics
   307  	origPath = filepath.FromSlash(origPath)
   308  
   309  	if origPath != "" && origPath[0] == os.PathSeparator && len(origPath) > 1 {
   310  		origPath = origPath[1:]
   311  	}
   312  	origPath = strings.TrimPrefix(origPath, "."+string(os.PathSeparator))
   313  
   314  	// Deal with wildcards
   315  	if allowWildcards && containsWildcards(origPath) {
   316  		var copyInfos []copyInfo
   317  		if err := b.context.Walk("", func(path string, info builder.FileInfo, err error) error {
   318  			if err != nil {
   319  				return err
   320  			}
   321  			if info.Name() == "" {
   322  				// Why are we doing this check?
   323  				return nil
   324  			}
   325  			if match, _ := filepath.Match(origPath, path); !match {
   326  				return nil
   327  			}
   328  
   329  			// Note we set allowWildcards to false in case the name has
   330  			// a * in it
   331  			subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false)
   332  			if err != nil {
   333  				return err
   334  			}
   335  			copyInfos = append(copyInfos, subInfos...)
   336  			return nil
   337  		}); err != nil {
   338  			return nil, err
   339  		}
   340  		return copyInfos, nil
   341  	}
   342  
   343  	// Must be a dir or a file
   344  
   345  	statPath, fi, err := b.context.Stat(origPath)
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	copyInfos := []copyInfo{{FileInfo: fi, decompress: allowLocalDecompression}}
   351  
   352  	hfi, handleHash := fi.(builder.Hashed)
   353  	if !handleHash {
   354  		return copyInfos, nil
   355  	}
   356  
   357  	// Deal with the single file case
   358  	if !fi.IsDir() {
   359  		hfi.SetHash("file:" + hfi.Hash())
   360  		return copyInfos, nil
   361  	}
   362  	// Must be a dir
   363  	var subfiles []string
   364  	err = b.context.Walk(statPath, func(path string, info builder.FileInfo, err error) error {
   365  		if err != nil {
   366  			return err
   367  		}
   368  		// we already checked handleHash above
   369  		subfiles = append(subfiles, info.(builder.Hashed).Hash())
   370  		return nil
   371  	})
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	sort.Strings(subfiles)
   377  	hasher := sha256.New()
   378  	hasher.Write([]byte(strings.Join(subfiles, ",")))
   379  	hfi.SetHash("dir:" + hex.EncodeToString(hasher.Sum(nil)))
   380  
   381  	return copyInfos, nil
   382  }
   383  
   384  func containsWildcards(name string) bool {
   385  	for i := 0; i < len(name); i++ {
   386  		ch := name[i]
   387  		if ch == '\\' {
   388  			i++
   389  		} else if ch == '*' || ch == '?' || ch == '[' {
   390  			return true
   391  		}
   392  	}
   393  	return false
   394  }
   395  
   396  func (b *Builder) processImageFrom(img builder.Image) error {
   397  	if img != nil {
   398  		b.image = img.ImageID()
   399  
   400  		if img.RunConfig() != nil {
   401  			b.runConfig = img.RunConfig()
   402  		}
   403  	}
   404  
   405  	// Check to see if we have a default PATH, note that windows won't
   406  	// have one as its set by HCS
   407  	if system.DefaultPathEnv != "" {
   408  		// Convert the slice of strings that represent the current list
   409  		// of env vars into a map so we can see if PATH is already set.
   410  		// If its not set then go ahead and give it our default value
   411  		configEnv := opts.ConvertKVStringsToMap(b.runConfig.Env)
   412  		if _, ok := configEnv["PATH"]; !ok {
   413  			b.runConfig.Env = append(b.runConfig.Env,
   414  				"PATH="+system.DefaultPathEnv)
   415  		}
   416  	}
   417  
   418  	if img == nil {
   419  		// Typically this means they used "FROM scratch"
   420  		return nil
   421  	}
   422  
   423  	// Process ONBUILD triggers if they exist
   424  	if nTriggers := len(b.runConfig.OnBuild); nTriggers != 0 {
   425  		word := "trigger"
   426  		if nTriggers > 1 {
   427  			word = "triggers"
   428  		}
   429  		fmt.Fprintf(b.Stderr, "# Executing %d build %s...\n", nTriggers, word)
   430  	}
   431  
   432  	// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
   433  	onBuildTriggers := b.runConfig.OnBuild
   434  	b.runConfig.OnBuild = []string{}
   435  
   436  	// parse the ONBUILD triggers by invoking the parser
   437  	for _, step := range onBuildTriggers {
   438  		ast, err := parser.Parse(strings.NewReader(step))
   439  		if err != nil {
   440  			return err
   441  		}
   442  
   443  		for i, n := range ast.Children {
   444  			switch strings.ToUpper(n.Value) {
   445  			case "ONBUILD":
   446  				return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
   447  			case "MAINTAINER", "FROM":
   448  				return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value)
   449  			}
   450  
   451  			if err := b.dispatch(i, n); err != nil {
   452  				return err
   453  			}
   454  		}
   455  	}
   456  
   457  	return nil
   458  }
   459  
   460  // probeCache checks if `b.docker` implements builder.ImageCache and image-caching
   461  // is enabled (`b.UseCache`).
   462  // If so attempts to look up the current `b.image` and `b.runConfig` pair with `b.docker`.
   463  // If an image is found, probeCache returns `(true, nil)`.
   464  // If no image is found, it returns `(false, nil)`.
   465  // If there is any error, it returns `(false, err)`.
   466  func (b *Builder) probeCache() (bool, error) {
   467  	c, ok := b.docker.(builder.ImageCache)
   468  	if !ok || b.options.NoCache || b.cacheBusted {
   469  		return false, nil
   470  	}
   471  	cache, err := c.GetCachedImageOnBuild(b.image, b.runConfig)
   472  	if err != nil {
   473  		return false, err
   474  	}
   475  	if len(cache) == 0 {
   476  		logrus.Debugf("[BUILDER] Cache miss: %s", b.runConfig.Cmd)
   477  		b.cacheBusted = true
   478  		return false, nil
   479  	}
   480  
   481  	fmt.Fprintf(b.Stdout, " ---> Using cache\n")
   482  	logrus.Debugf("[BUILDER] Use cached version: %s", b.runConfig.Cmd)
   483  	b.image = string(cache)
   484  
   485  	return true, nil
   486  }
   487  
   488  func (b *Builder) create() (string, error) {
   489  	if b.image == "" && !b.noBaseImage {
   490  		return "", fmt.Errorf("Please provide a source image with `from` prior to run")
   491  	}
   492  	b.runConfig.Image = b.image
   493  
   494  	resources := container.Resources{
   495  		CgroupParent: b.options.CgroupParent,
   496  		CPUShares:    b.options.CPUShares,
   497  		CPUPeriod:    b.options.CPUPeriod,
   498  		CPUQuota:     b.options.CPUQuota,
   499  		CpusetCpus:   b.options.CPUSetCPUs,
   500  		CpusetMems:   b.options.CPUSetMems,
   501  		Memory:       b.options.Memory,
   502  		MemorySwap:   b.options.MemorySwap,
   503  		Ulimits:      b.options.Ulimits,
   504  	}
   505  
   506  	// TODO: why not embed a hostconfig in builder?
   507  	hostConfig := &container.HostConfig{
   508  		Isolation: b.options.Isolation,
   509  		ShmSize:   b.options.ShmSize,
   510  		Resources: resources,
   511  	}
   512  
   513  	config := *b.runConfig
   514  
   515  	// Create the container
   516  	c, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
   517  		Config:     b.runConfig,
   518  		HostConfig: hostConfig,
   519  	})
   520  	if err != nil {
   521  		return "", err
   522  	}
   523  	for _, warning := range c.Warnings {
   524  		fmt.Fprintf(b.Stdout, " ---> [Warning] %s\n", warning)
   525  	}
   526  
   527  	b.tmpContainers[c.ID] = struct{}{}
   528  	fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(c.ID))
   529  
   530  	if len(config.Cmd) > 0 {
   531  		// override the entry point that may have been picked up from the base image
   532  		if err := b.docker.ContainerUpdateCmdOnBuild(c.ID, config.Cmd); err != nil {
   533  			return "", err
   534  		}
   535  	}
   536  
   537  	return c.ID, nil
   538  }
   539  
   540  func (b *Builder) run(cID string) (err error) {
   541  	errCh := make(chan error)
   542  	go func() {
   543  		errCh <- b.docker.ContainerAttachRaw(cID, nil, b.Stdout, b.Stderr, true)
   544  	}()
   545  
   546  	finished := make(chan struct{})
   547  	defer close(finished)
   548  	go func() {
   549  		select {
   550  		case <-b.cancelled:
   551  			logrus.Debugln("Build cancelled, killing and removing container:", cID)
   552  			b.docker.ContainerKill(cID, 0)
   553  			b.removeContainer(cID)
   554  		case <-finished:
   555  		}
   556  	}()
   557  
   558  	if err := b.docker.ContainerStart(cID, nil); err != nil {
   559  		return err
   560  	}
   561  
   562  	// Block on reading output from container, stop on err or chan closed
   563  	if err := <-errCh; err != nil {
   564  		return err
   565  	}
   566  
   567  	if ret, _ := b.docker.ContainerWait(cID, -1); ret != 0 {
   568  		// TODO: change error type, because jsonmessage.JSONError assumes HTTP
   569  		return &jsonmessage.JSONError{
   570  			Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(b.runConfig.Cmd, " "), ret),
   571  			Code:    ret,
   572  		}
   573  	}
   574  
   575  	return nil
   576  }
   577  
   578  func (b *Builder) removeContainer(c string) error {
   579  	rmConfig := &types.ContainerRmConfig{
   580  		ForceRemove:  true,
   581  		RemoveVolume: true,
   582  	}
   583  	if err := b.docker.ContainerRm(c, rmConfig); err != nil {
   584  		fmt.Fprintf(b.Stdout, "Error removing intermediate container %s: %v\n", stringid.TruncateID(c), err)
   585  		return err
   586  	}
   587  	return nil
   588  }
   589  
   590  func (b *Builder) clearTmp() {
   591  	for c := range b.tmpContainers {
   592  		if err := b.removeContainer(c); err != nil {
   593  			return
   594  		}
   595  		delete(b.tmpContainers, c)
   596  		fmt.Fprintf(b.Stdout, "Removing intermediate container %s\n", stringid.TruncateID(c))
   597  	}
   598  }
   599  
   600  // readDockerfile reads a Dockerfile from the current context.
   601  func (b *Builder) readDockerfile() error {
   602  	// If no -f was specified then look for 'Dockerfile'. If we can't find
   603  	// that then look for 'dockerfile'.  If neither are found then default
   604  	// back to 'Dockerfile' and use that in the error message.
   605  	if b.options.Dockerfile == "" {
   606  		b.options.Dockerfile = builder.DefaultDockerfileName
   607  		if _, _, err := b.context.Stat(b.options.Dockerfile); os.IsNotExist(err) {
   608  			lowercase := strings.ToLower(b.options.Dockerfile)
   609  			if _, _, err := b.context.Stat(lowercase); err == nil {
   610  				b.options.Dockerfile = lowercase
   611  			}
   612  		}
   613  	}
   614  
   615  	f, err := b.context.Open(b.options.Dockerfile)
   616  	if err != nil {
   617  		if os.IsNotExist(err) {
   618  			return fmt.Errorf("Cannot locate specified Dockerfile: %s", b.options.Dockerfile)
   619  		}
   620  		return err
   621  	}
   622  	if f, ok := f.(*os.File); ok {
   623  		// ignoring error because Open already succeeded
   624  		fi, err := f.Stat()
   625  		if err != nil {
   626  			return fmt.Errorf("Unexpected error reading Dockerfile: %v", err)
   627  		}
   628  		if fi.Size() == 0 {
   629  			return fmt.Errorf("The Dockerfile (%s) cannot be empty", b.options.Dockerfile)
   630  		}
   631  	}
   632  	b.dockerfile, err = parser.Parse(f)
   633  	f.Close()
   634  	if err != nil {
   635  		return err
   636  	}
   637  
   638  	// After the Dockerfile has been parsed, we need to check the .dockerignore
   639  	// file for either "Dockerfile" or ".dockerignore", and if either are
   640  	// present then erase them from the build context. These files should never
   641  	// have been sent from the client but we did send them to make sure that
   642  	// we had the Dockerfile to actually parse, and then we also need the
   643  	// .dockerignore file to know whether either file should be removed.
   644  	// Note that this assumes the Dockerfile has been read into memory and
   645  	// is now safe to be removed.
   646  	if dockerIgnore, ok := b.context.(builder.DockerIgnoreContext); ok {
   647  		dockerIgnore.Process([]string{b.options.Dockerfile})
   648  	}
   649  	return nil
   650  }
   651  
   652  // determine if build arg is part of built-in args or user
   653  // defined args in Dockerfile at any point in time.
   654  func (b *Builder) isBuildArgAllowed(arg string) bool {
   655  	if _, ok := BuiltinAllowedBuildArgs[arg]; ok {
   656  		return true
   657  	}
   658  	if _, ok := b.allowedBuildArgs[arg]; ok {
   659  		return true
   660  	}
   661  	return false
   662  }