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