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