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