github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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  	"sort"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/Sirupsen/logrus"
    22  	"github.com/docker/docker/api/types"
    23  	"github.com/docker/docker/api/types/backend"
    24  	"github.com/docker/docker/api/types/container"
    25  	"github.com/docker/docker/api/types/strslice"
    26  	"github.com/docker/docker/builder"
    27  	"github.com/docker/docker/builder/dockerfile/parser"
    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/system"
    36  	"github.com/docker/docker/pkg/tarsum"
    37  	"github.com/docker/docker/pkg/urlutil"
    38  	"github.com/docker/docker/runconfig/opts"
    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 errors.New("Please provide a source image with `from` prior to commit")
    47  	}
    48  	b.runConfig.Image = b.image
    49  
    50  	if id == "" {
    51  		cmd := b.runConfig.Cmd
    52  		b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) ", comment))
    53  		defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
    54  
    55  		hit, err := b.probeCache()
    56  		if err != nil {
    57  			return err
    58  		} else if hit {
    59  			return nil
    60  		}
    61  		id, err = b.create()
    62  		if err != nil {
    63  			return err
    64  		}
    65  	}
    66  
    67  	// Note: Actually copy the struct
    68  	autoConfig := *b.runConfig
    69  	autoConfig.Cmd = autoCmd
    70  
    71  	commitCfg := &backend.ContainerCommitConfig{
    72  		ContainerCommitConfig: types.ContainerCommitConfig{
    73  			Author: b.maintainer,
    74  			Pause:  true,
    75  			Config: &autoConfig,
    76  		},
    77  	}
    78  
    79  	// Commit the container
    80  	imageID, err := b.docker.Commit(id, commitCfg)
    81  	if err != nil {
    82  		return err
    83  	}
    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  		if urlutil.IsURL(orig) {
   117  			if !allowRemote {
   118  				return fmt.Errorf("Source can't be a URL for %s", cmdName)
   119  			}
   120  			fi, err = b.download(orig)
   121  			if err != nil {
   122  				return err
   123  			}
   124  			defer os.RemoveAll(filepath.Dir(fi.Path()))
   125  			infos = append(infos, copyInfo{
   126  				FileInfo:   fi,
   127  				decompress: false,
   128  			})
   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 errors.New("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  	b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest)))
   176  	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
   177  
   178  	if hit, err := b.probeCache(); err != nil {
   179  		return err
   180  	} else if hit {
   181  		return nil
   182  	}
   183  
   184  	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
   185  		Config: b.runConfig,
   186  		// Set a log config to override any default value set on the daemon
   187  		HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
   188  	})
   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 it's a relative path - meaning, make it
   197  	// relative to the WORKINGDIR
   198  	if dest, err = normaliseDest(cmdName, b.runConfig.WorkingDir, dest); err != nil {
   199  		return err
   200  	}
   201  
   202  	for _, info := range infos {
   203  		if err := b.docker.CopyOnBuild(container.ID, dest, info.FileInfo, info.decompress); err != nil {
   204  			return err
   205  		}
   206  	}
   207  
   208  	return b.commit(container.ID, cmd, comment)
   209  }
   210  
   211  func (b *Builder) download(srcURL string) (fi builder.FileInfo, err error) {
   212  	// get filename from URL
   213  	u, err := url.Parse(srcURL)
   214  	if err != nil {
   215  		return
   216  	}
   217  	path := filepath.FromSlash(u.Path) // Ensure in platform semantics
   218  	if strings.HasSuffix(path, string(os.PathSeparator)) {
   219  		path = path[:len(path)-1]
   220  	}
   221  	parts := strings.Split(path, string(os.PathSeparator))
   222  	filename := parts[len(parts)-1]
   223  	if filename == "" {
   224  		err = fmt.Errorf("cannot determine filename from url: %s", u)
   225  		return
   226  	}
   227  
   228  	// Initiate the download
   229  	resp, err := httputils.Download(srcURL)
   230  	if err != nil {
   231  		return
   232  	}
   233  
   234  	// Prepare file in a tmp dir
   235  	tmpDir, err := ioutils.TempDir("", "docker-remote")
   236  	if err != nil {
   237  		return
   238  	}
   239  	defer func() {
   240  		if err != nil {
   241  			os.RemoveAll(tmpDir)
   242  		}
   243  	}()
   244  	tmpFileName := filepath.Join(tmpDir, filename)
   245  	tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
   246  	if err != nil {
   247  		return
   248  	}
   249  
   250  	stdoutFormatter := b.Stdout.(*streamformatter.StdoutFormatter)
   251  	progressOutput := stdoutFormatter.StreamFormatter.NewProgressOutput(stdoutFormatter.Writer, true)
   252  	progressReader := progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Downloading")
   253  	// Download and dump result to tmp file
   254  	if _, err = io.Copy(tmpFile, progressReader); err != nil {
   255  		tmpFile.Close()
   256  		return
   257  	}
   258  	fmt.Fprintln(b.Stdout)
   259  	// ignoring error because the file was already opened successfully
   260  	tmpFileSt, err := tmpFile.Stat()
   261  	if err != nil {
   262  		tmpFile.Close()
   263  		return
   264  	}
   265  
   266  	// Set the mtime to the Last-Modified header value if present
   267  	// Otherwise just remove atime and mtime
   268  	mTime := time.Time{}
   269  
   270  	lastMod := resp.Header.Get("Last-Modified")
   271  	if lastMod != "" {
   272  		// If we can't parse it then just let it default to 'zero'
   273  		// otherwise use the parsed time value
   274  		if parsedMTime, err := http.ParseTime(lastMod); err == nil {
   275  			mTime = parsedMTime
   276  		}
   277  	}
   278  
   279  	tmpFile.Close()
   280  
   281  	if err = system.Chtimes(tmpFileName, mTime, mTime); err != nil {
   282  		return
   283  	}
   284  
   285  	// Calc the checksum, even if we're using the cache
   286  	r, err := archive.Tar(tmpFileName, archive.Uncompressed)
   287  	if err != nil {
   288  		return
   289  	}
   290  	tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version1)
   291  	if err != nil {
   292  		return
   293  	}
   294  	if _, err = io.Copy(ioutil.Discard, tarSum); err != nil {
   295  		return
   296  	}
   297  	hash := tarSum.Sum(nil)
   298  	r.Close()
   299  	return &builder.HashedFileInfo{FileInfo: builder.PathFileInfo{FileInfo: tmpFileSt, FilePath: tmpFileName}, FileHash: hash}, nil
   300  }
   301  
   302  func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool) ([]copyInfo, error) {
   303  
   304  	// Work in daemon-specific OS filepath semantics
   305  	origPath = filepath.FromSlash(origPath)
   306  
   307  	if origPath != "" && origPath[0] == os.PathSeparator && len(origPath) > 1 {
   308  		origPath = origPath[1:]
   309  	}
   310  	origPath = strings.TrimPrefix(origPath, "."+string(os.PathSeparator))
   311  
   312  	// Deal with wildcards
   313  	if allowWildcards && containsWildcards(origPath) {
   314  		var copyInfos []copyInfo
   315  		if err := b.context.Walk("", func(path string, info builder.FileInfo, err error) error {
   316  			if err != nil {
   317  				return err
   318  			}
   319  			if info.Name() == "" {
   320  				// Why are we doing this check?
   321  				return nil
   322  			}
   323  			if match, _ := filepath.Match(origPath, path); !match {
   324  				return nil
   325  			}
   326  
   327  			// Note we set allowWildcards to false in case the name has
   328  			// a * in it
   329  			subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false)
   330  			if err != nil {
   331  				return err
   332  			}
   333  			copyInfos = append(copyInfos, subInfos...)
   334  			return nil
   335  		}); err != nil {
   336  			return nil, err
   337  		}
   338  		return copyInfos, nil
   339  	}
   340  
   341  	// Must be a dir or a file
   342  
   343  	statPath, fi, err := b.context.Stat(origPath)
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  
   348  	copyInfos := []copyInfo{{FileInfo: fi, decompress: allowLocalDecompression}}
   349  
   350  	hfi, handleHash := fi.(builder.Hashed)
   351  	if !handleHash {
   352  		return copyInfos, nil
   353  	}
   354  
   355  	// Deal with the single file case
   356  	if !fi.IsDir() {
   357  		hfi.SetHash("file:" + hfi.Hash())
   358  		return copyInfos, nil
   359  	}
   360  	// Must be a dir
   361  	var subfiles []string
   362  	err = b.context.Walk(statPath, func(path string, info builder.FileInfo, err error) error {
   363  		if err != nil {
   364  			return err
   365  		}
   366  		// we already checked handleHash above
   367  		subfiles = append(subfiles, info.(builder.Hashed).Hash())
   368  		return nil
   369  	})
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  
   374  	sort.Strings(subfiles)
   375  	hasher := sha256.New()
   376  	hasher.Write([]byte(strings.Join(subfiles, ",")))
   377  	hfi.SetHash("dir:" + hex.EncodeToString(hasher.Sum(nil)))
   378  
   379  	return copyInfos, nil
   380  }
   381  
   382  func (b *Builder) processImageFrom(img builder.Image) error {
   383  	if img != nil {
   384  		b.image = img.ImageID()
   385  
   386  		if img.RunConfig() != nil {
   387  			b.runConfig = img.RunConfig()
   388  		}
   389  	}
   390  
   391  	// Check to see if we have a default PATH, note that windows won't
   392  	// have one as it's set by HCS
   393  	if system.DefaultPathEnv != "" {
   394  		// Convert the slice of strings that represent the current list
   395  		// of env vars into a map so we can see if PATH is already set.
   396  		// If it's not set then go ahead and give it our default value
   397  		configEnv := opts.ConvertKVStringsToMap(b.runConfig.Env)
   398  		if _, ok := configEnv["PATH"]; !ok {
   399  			b.runConfig.Env = append(b.runConfig.Env,
   400  				"PATH="+system.DefaultPathEnv)
   401  		}
   402  	}
   403  
   404  	if img == nil {
   405  		// Typically this means they used "FROM scratch"
   406  		return nil
   407  	}
   408  
   409  	// Process ONBUILD triggers if they exist
   410  	if nTriggers := len(b.runConfig.OnBuild); nTriggers != 0 {
   411  		word := "trigger"
   412  		if nTriggers > 1 {
   413  			word = "triggers"
   414  		}
   415  		fmt.Fprintf(b.Stderr, "# Executing %d build %s...\n", nTriggers, word)
   416  	}
   417  
   418  	// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
   419  	onBuildTriggers := b.runConfig.OnBuild
   420  	b.runConfig.OnBuild = []string{}
   421  
   422  	// Reset stdin settings as all build actions run without stdin
   423  	b.runConfig.OpenStdin = false
   424  	b.runConfig.StdinOnce = false
   425  
   426  	// parse the ONBUILD triggers by invoking the parser
   427  	for _, step := range onBuildTriggers {
   428  		ast, err := parser.Parse(strings.NewReader(step), &b.directive)
   429  		if err != nil {
   430  			return err
   431  		}
   432  
   433  		total := len(ast.Children)
   434  		for _, n := range ast.Children {
   435  			if err := b.checkDispatch(n, true); err != nil {
   436  				return err
   437  			}
   438  		}
   439  		for i, n := range ast.Children {
   440  			if err := b.dispatch(i, total, n); err != nil {
   441  				return err
   442  			}
   443  		}
   444  	}
   445  
   446  	return nil
   447  }
   448  
   449  // probeCache checks if cache match can be found for current build instruction.
   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 := b.imageCache
   455  	if c == nil || b.options.NoCache || b.cacheBusted {
   456  		return false, nil
   457  	}
   458  	cache, err := c.GetCache(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.Fprint(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 "", errors.New("Please provide a source image with `from` prior to run")
   478  	}
   479  	b.runConfig.Image = b.image
   480  
   481  	resources := container.Resources{
   482  		CgroupParent: b.options.CgroupParent,
   483  		CPUShares:    b.options.CPUShares,
   484  		CPUPeriod:    b.options.CPUPeriod,
   485  		CPUQuota:     b.options.CPUQuota,
   486  		CpusetCpus:   b.options.CPUSetCPUs,
   487  		CpusetMems:   b.options.CPUSetMems,
   488  		Memory:       b.options.Memory,
   489  		MemorySwap:   b.options.MemorySwap,
   490  		Ulimits:      b.options.Ulimits,
   491  	}
   492  
   493  	// TODO: why not embed a hostconfig in builder?
   494  	hostConfig := &container.HostConfig{
   495  		SecurityOpt: b.options.SecurityOpt,
   496  		Isolation:   b.options.Isolation,
   497  		ShmSize:     b.options.ShmSize,
   498  		Resources:   resources,
   499  		NetworkMode: container.NetworkMode(b.options.NetworkMode),
   500  		// Set a log config to override any default value set on the daemon
   501  		LogConfig: defaultLogConfig,
   502  	}
   503  
   504  	config := *b.runConfig
   505  
   506  	// Create the container
   507  	c, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
   508  		Config:     b.runConfig,
   509  		HostConfig: hostConfig,
   510  	})
   511  	if err != nil {
   512  		return "", err
   513  	}
   514  	for _, warning := range c.Warnings {
   515  		fmt.Fprintf(b.Stdout, " ---> [Warning] %s\n", warning)
   516  	}
   517  
   518  	b.tmpContainers[c.ID] = struct{}{}
   519  	fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(c.ID))
   520  
   521  	// override the entry point that may have been picked up from the base image
   522  	if err := b.docker.ContainerUpdateCmdOnBuild(c.ID, config.Cmd); err != nil {
   523  		return "", err
   524  	}
   525  
   526  	return c.ID, nil
   527  }
   528  
   529  var errCancelled = errors.New("build cancelled")
   530  
   531  func (b *Builder) run(cID string) (err error) {
   532  	errCh := make(chan error)
   533  	go func() {
   534  		errCh <- b.docker.ContainerAttachRaw(cID, nil, b.Stdout, b.Stderr, true)
   535  	}()
   536  
   537  	finished := make(chan struct{})
   538  	cancelErrCh := make(chan error, 1)
   539  	go func() {
   540  		select {
   541  		case <-b.clientCtx.Done():
   542  			logrus.Debugln("Build cancelled, killing and removing container:", cID)
   543  			b.docker.ContainerKill(cID, 0)
   544  			b.removeContainer(cID)
   545  			cancelErrCh <- errCancelled
   546  		case <-finished:
   547  			cancelErrCh <- nil
   548  		}
   549  	}()
   550  
   551  	if err := b.docker.ContainerStart(cID, nil, "", ""); err != nil {
   552  		close(finished)
   553  		if cancelErr := <-cancelErrCh; cancelErr != nil {
   554  			logrus.Debugf("Build cancelled (%v) and got an error from ContainerStart: %v",
   555  				cancelErr, err)
   556  		}
   557  		return err
   558  	}
   559  
   560  	// Block on reading output from container, stop on err or chan closed
   561  	if err := <-errCh; err != nil {
   562  		close(finished)
   563  		if cancelErr := <-cancelErrCh; cancelErr != nil {
   564  			logrus.Debugf("Build cancelled (%v) and got an error from errCh: %v",
   565  				cancelErr, err)
   566  		}
   567  		return err
   568  	}
   569  
   570  	if ret, _ := b.docker.ContainerWait(cID, -1); ret != 0 {
   571  		close(finished)
   572  		if cancelErr := <-cancelErrCh; cancelErr != nil {
   573  			logrus.Debugf("Build cancelled (%v) and got a non-zero code from ContainerWait: %d",
   574  				cancelErr, ret)
   575  		}
   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", strings.Join(b.runConfig.Cmd, " "), ret),
   579  			Code:    ret,
   580  		}
   581  	}
   582  	close(finished)
   583  	return <-cancelErrCh
   584  }
   585  
   586  func (b *Builder) removeContainer(c string) error {
   587  	rmConfig := &types.ContainerRmConfig{
   588  		ForceRemove:  true,
   589  		RemoveVolume: true,
   590  	}
   591  	if err := b.docker.ContainerRm(c, rmConfig); err != nil {
   592  		fmt.Fprintf(b.Stdout, "Error removing intermediate container %s: %v\n", stringid.TruncateID(c), err)
   593  		return err
   594  	}
   595  	return nil
   596  }
   597  
   598  func (b *Builder) clearTmp() {
   599  	for c := range b.tmpContainers {
   600  		if err := b.removeContainer(c); err != nil {
   601  			return
   602  		}
   603  		delete(b.tmpContainers, c)
   604  		fmt.Fprintf(b.Stdout, "Removing intermediate container %s\n", stringid.TruncateID(c))
   605  	}
   606  }
   607  
   608  // readDockerfile reads a Dockerfile from the current context.
   609  func (b *Builder) readDockerfile() error {
   610  	// If no -f was specified then look for 'Dockerfile'. If we can't find
   611  	// that then look for 'dockerfile'.  If neither are found then default
   612  	// back to 'Dockerfile' and use that in the error message.
   613  	if b.options.Dockerfile == "" {
   614  		b.options.Dockerfile = builder.DefaultDockerfileName
   615  		if _, _, err := b.context.Stat(b.options.Dockerfile); os.IsNotExist(err) {
   616  			lowercase := strings.ToLower(b.options.Dockerfile)
   617  			if _, _, err := b.context.Stat(lowercase); err == nil {
   618  				b.options.Dockerfile = lowercase
   619  			}
   620  		}
   621  	}
   622  
   623  	err := b.parseDockerfile()
   624  
   625  	if err != nil {
   626  		return err
   627  	}
   628  
   629  	// After the Dockerfile has been parsed, we need to check the .dockerignore
   630  	// file for either "Dockerfile" or ".dockerignore", and if either are
   631  	// present then erase them from the build context. These files should never
   632  	// have been sent from the client but we did send them to make sure that
   633  	// we had the Dockerfile to actually parse, and then we also need the
   634  	// .dockerignore file to know whether either file should be removed.
   635  	// Note that this assumes the Dockerfile has been read into memory and
   636  	// is now safe to be removed.
   637  	if dockerIgnore, ok := b.context.(builder.DockerIgnoreContext); ok {
   638  		dockerIgnore.Process([]string{b.options.Dockerfile})
   639  	}
   640  	return nil
   641  }
   642  
   643  func (b *Builder) parseDockerfile() error {
   644  	f, err := b.context.Open(b.options.Dockerfile)
   645  	if err != nil {
   646  		if os.IsNotExist(err) {
   647  			return fmt.Errorf("Cannot locate specified Dockerfile: %s", b.options.Dockerfile)
   648  		}
   649  		return err
   650  	}
   651  	defer f.Close()
   652  	if f, ok := f.(*os.File); ok {
   653  		// ignoring error because Open already succeeded
   654  		fi, err := f.Stat()
   655  		if err != nil {
   656  			return fmt.Errorf("Unexpected error reading Dockerfile: %v", err)
   657  		}
   658  		if fi.Size() == 0 {
   659  			return fmt.Errorf("The Dockerfile (%s) cannot be empty", b.options.Dockerfile)
   660  		}
   661  	}
   662  	b.dockerfile, err = parser.Parse(f, &b.directive)
   663  	if err != nil {
   664  		return err
   665  	}
   666  
   667  	return nil
   668  }
   669  
   670  // determine if build arg is part of built-in args or user
   671  // defined args in Dockerfile at any point in time.
   672  func (b *Builder) isBuildArgAllowed(arg string) bool {
   673  	if _, ok := BuiltinAllowedBuildArgs[arg]; ok {
   674  		return true
   675  	}
   676  	if _, ok := b.allowedBuildArgs[arg]; ok {
   677  		return true
   678  	}
   679  	return false
   680  }