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