github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/api/client/build.go (about)

     1  package client
     2  
     3  import (
     4  	"archive/tar"
     5  	"bufio"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"regexp"
    13  	"runtime"
    14  	"strings"
    15  
    16  	"github.com/docker/docker/api"
    17  	"github.com/docker/docker/api/types"
    18  	"github.com/docker/docker/builder/dockerignore"
    19  	Cli "github.com/docker/docker/cli"
    20  	"github.com/docker/docker/opts"
    21  	"github.com/docker/docker/pkg/archive"
    22  	"github.com/docker/docker/pkg/fileutils"
    23  	"github.com/docker/docker/pkg/gitutils"
    24  	"github.com/docker/docker/pkg/httputils"
    25  	"github.com/docker/docker/pkg/jsonmessage"
    26  	flag "github.com/docker/docker/pkg/mflag"
    27  	"github.com/docker/docker/pkg/progress"
    28  	"github.com/docker/docker/pkg/streamformatter"
    29  	"github.com/docker/docker/pkg/ulimit"
    30  	"github.com/docker/docker/pkg/urlutil"
    31  	"github.com/docker/docker/reference"
    32  	"github.com/docker/docker/utils"
    33  	"github.com/docker/go-units"
    34  )
    35  
    36  // CmdBuild builds a new image from the source code at a given path.
    37  //
    38  // If '-' is provided instead of a path or URL, Docker will build an image from either a Dockerfile or tar archive read from STDIN.
    39  //
    40  // Usage: docker build [OPTIONS] PATH | URL | -
    41  func (cli *DockerCli) CmdBuild(args ...string) error {
    42  	cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
    43  	flTags := opts.NewListOpts(validateTag)
    44  	cmd.Var(&flTags, []string{"t", "-tag"}, "Name and optionally a tag in the 'name:tag' format")
    45  	suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
    46  	noCache := cmd.Bool([]string{"-no-cache"}, false, "Do not use cache when building the image")
    47  	rm := cmd.Bool([]string{"-rm"}, true, "Remove intermediate containers after a successful build")
    48  	forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
    49  	pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
    50  	dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
    51  	flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
    52  	flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
    53  	flShmSize := cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
    54  	flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
    55  	flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
    56  	flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
    57  	flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
    58  	flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
    59  	flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
    60  	flBuildArg := opts.NewListOpts(opts.ValidateEnv)
    61  	cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
    62  	isolation := cmd.String([]string{"-isolation"}, "", "Container isolation level")
    63  
    64  	ulimits := make(map[string]*ulimit.Ulimit)
    65  	flUlimits := opts.NewUlimitOpt(&ulimits)
    66  	cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
    67  
    68  	cmd.Require(flag.Exact, 1)
    69  
    70  	// For trusted pull on "FROM <image>" instruction.
    71  	addTrustedFlags(cmd, true)
    72  
    73  	cmd.ParseFlags(args, true)
    74  
    75  	var (
    76  		context  io.ReadCloser
    77  		isRemote bool
    78  		err      error
    79  	)
    80  
    81  	_, err = exec.LookPath("git")
    82  	hasGit := err == nil
    83  
    84  	specifiedContext := cmd.Arg(0)
    85  
    86  	var (
    87  		contextDir    string
    88  		tempDir       string
    89  		relDockerfile string
    90  	)
    91  
    92  	switch {
    93  	case specifiedContext == "-":
    94  		tempDir, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
    95  	case urlutil.IsGitURL(specifiedContext) && hasGit:
    96  		tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
    97  	case urlutil.IsURL(specifiedContext):
    98  		tempDir, relDockerfile, err = getContextFromURL(cli.out, specifiedContext, *dockerfileName)
    99  	default:
   100  		contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
   101  	}
   102  
   103  	if err != nil {
   104  		return fmt.Errorf("unable to prepare context: %s", err)
   105  	}
   106  
   107  	if tempDir != "" {
   108  		defer os.RemoveAll(tempDir)
   109  		contextDir = tempDir
   110  	}
   111  
   112  	// Resolve the FROM lines in the Dockerfile to trusted digest references
   113  	// using Notary. On a successful build, we must tag the resolved digests
   114  	// to the original name specified in the Dockerfile.
   115  	newDockerfile, resolvedTags, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference)
   116  	if err != nil {
   117  		return fmt.Errorf("unable to process Dockerfile: %v", err)
   118  	}
   119  	defer newDockerfile.Close()
   120  
   121  	// And canonicalize dockerfile name to a platform-independent one
   122  	relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
   123  	if err != nil {
   124  		return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
   125  	}
   126  
   127  	f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
   128  	if err != nil && !os.IsNotExist(err) {
   129  		return err
   130  	}
   131  
   132  	var excludes []string
   133  	if err == nil {
   134  		excludes, err = dockerignore.ReadAll(f)
   135  		if err != nil {
   136  			return err
   137  		}
   138  	}
   139  
   140  	if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
   141  		return fmt.Errorf("Error checking context: '%s'.", err)
   142  	}
   143  
   144  	// If .dockerignore mentions .dockerignore or the Dockerfile
   145  	// then make sure we send both files over to the daemon
   146  	// because Dockerfile is, obviously, needed no matter what, and
   147  	// .dockerignore is needed to know if either one needs to be
   148  	// removed. The daemon will remove them for us, if needed, after it
   149  	// parses the Dockerfile. Ignore errors here, as they will have been
   150  	// caught by ValidateContextDirectory above.
   151  	var includes = []string{"."}
   152  	keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
   153  	keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
   154  	if keepThem1 || keepThem2 {
   155  		includes = append(includes, ".dockerignore", relDockerfile)
   156  	}
   157  
   158  	context, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
   159  		Compression:     archive.Uncompressed,
   160  		ExcludePatterns: excludes,
   161  		IncludeFiles:    includes,
   162  	})
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	// Wrap the tar archive to replace the Dockerfile entry with the rewritten
   168  	// Dockerfile which uses trusted pulls.
   169  	context = replaceDockerfileTarWrapper(context, newDockerfile, relDockerfile)
   170  
   171  	// Setup an upload progress bar
   172  	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(cli.out, true)
   173  
   174  	var body io.Reader = progress.NewProgressReader(context, progressOutput, 0, "", "Sending build context to Docker daemon")
   175  
   176  	var memory int64
   177  	if *flMemoryString != "" {
   178  		parsedMemory, err := units.RAMInBytes(*flMemoryString)
   179  		if err != nil {
   180  			return err
   181  		}
   182  		memory = parsedMemory
   183  	}
   184  
   185  	var memorySwap int64
   186  	if *flMemorySwap != "" {
   187  		if *flMemorySwap == "-1" {
   188  			memorySwap = -1
   189  		} else {
   190  			parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
   191  			if err != nil {
   192  				return err
   193  			}
   194  			memorySwap = parsedMemorySwap
   195  		}
   196  	}
   197  
   198  	var remoteContext string
   199  	if isRemote {
   200  		remoteContext = cmd.Arg(0)
   201  	}
   202  
   203  	options := types.ImageBuildOptions{
   204  		Context:        body,
   205  		Memory:         memory,
   206  		MemorySwap:     memorySwap,
   207  		Tags:           flTags.GetAll(),
   208  		SuppressOutput: *suppressOutput,
   209  		RemoteContext:  remoteContext,
   210  		NoCache:        *noCache,
   211  		Remove:         *rm,
   212  		ForceRemove:    *forceRm,
   213  		PullParent:     *pull,
   214  		Isolation:      *isolation,
   215  		CPUSetCPUs:     *flCPUSetCpus,
   216  		CPUSetMems:     *flCPUSetMems,
   217  		CPUShares:      *flCPUShares,
   218  		CPUQuota:       *flCPUQuota,
   219  		CPUPeriod:      *flCPUPeriod,
   220  		CgroupParent:   *flCgroupParent,
   221  		ShmSize:        *flShmSize,
   222  		Dockerfile:     relDockerfile,
   223  		Ulimits:        flUlimits.GetList(),
   224  		BuildArgs:      flBuildArg.GetAll(),
   225  		AuthConfigs:    cli.configFile.AuthConfigs,
   226  	}
   227  
   228  	response, err := cli.client.ImageBuild(options)
   229  	if err != nil {
   230  		return err
   231  	}
   232  
   233  	err = jsonmessage.DisplayJSONMessagesStream(response.Body, cli.out, cli.outFd, cli.isTerminalOut)
   234  	if err != nil {
   235  		if jerr, ok := err.(*jsonmessage.JSONError); ok {
   236  			// If no error code is set, default to 1
   237  			if jerr.Code == 0 {
   238  				jerr.Code = 1
   239  			}
   240  			return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
   241  		}
   242  	}
   243  
   244  	// Windows: show error message about modified file permissions.
   245  	if response.OSType == "windows" {
   246  		fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
   247  	}
   248  
   249  	// Since the build was successful, now we must tag any of the resolved
   250  	// images from the above Dockerfile rewrite.
   251  	for _, resolved := range resolvedTags {
   252  		if err := cli.tagTrusted(resolved.digestRef, resolved.tagRef); err != nil {
   253  			return err
   254  		}
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  // validateTag checks if the given image name can be resolved.
   261  func validateTag(rawRepo string) (string, error) {
   262  	_, err := reference.ParseNamed(rawRepo)
   263  	if err != nil {
   264  		return "", err
   265  	}
   266  
   267  	return rawRepo, nil
   268  }
   269  
   270  // isUNC returns true if the path is UNC (one starting \\). It always returns
   271  // false on Linux.
   272  func isUNC(path string) bool {
   273  	return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
   274  }
   275  
   276  // getDockerfileRelPath uses the given context directory for a `docker build`
   277  // and returns the absolute path to the context directory, the relative path of
   278  // the dockerfile in that context directory, and a non-nil error on success.
   279  func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
   280  	if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
   281  		return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
   282  	}
   283  
   284  	// The context dir might be a symbolic link, so follow it to the actual
   285  	// target directory.
   286  	//
   287  	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
   288  	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
   289  	// paths (those starting with \\). This hack means that when using links
   290  	// on UNC paths, they will not be followed.
   291  	if !isUNC(absContextDir) {
   292  		absContextDir, err = filepath.EvalSymlinks(absContextDir)
   293  		if err != nil {
   294  			return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
   295  		}
   296  	}
   297  
   298  	stat, err := os.Lstat(absContextDir)
   299  	if err != nil {
   300  		return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
   301  	}
   302  
   303  	if !stat.IsDir() {
   304  		return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
   305  	}
   306  
   307  	absDockerfile := givenDockerfile
   308  	if absDockerfile == "" {
   309  		// No -f/--file was specified so use the default relative to the
   310  		// context directory.
   311  		absDockerfile = filepath.Join(absContextDir, api.DefaultDockerfileName)
   312  
   313  		// Just to be nice ;-) look for 'dockerfile' too but only
   314  		// use it if we found it, otherwise ignore this check
   315  		if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
   316  			altPath := filepath.Join(absContextDir, strings.ToLower(api.DefaultDockerfileName))
   317  			if _, err = os.Lstat(altPath); err == nil {
   318  				absDockerfile = altPath
   319  			}
   320  		}
   321  	}
   322  
   323  	// If not already an absolute path, the Dockerfile path should be joined to
   324  	// the base directory.
   325  	if !filepath.IsAbs(absDockerfile) {
   326  		absDockerfile = filepath.Join(absContextDir, absDockerfile)
   327  	}
   328  
   329  	// Evaluate symlinks in the path to the Dockerfile too.
   330  	//
   331  	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
   332  	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
   333  	// paths (those starting with \\). This hack means that when using links
   334  	// on UNC paths, they will not be followed.
   335  	if !isUNC(absDockerfile) {
   336  		absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
   337  		if err != nil {
   338  			return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
   339  		}
   340  	}
   341  
   342  	if _, err := os.Lstat(absDockerfile); err != nil {
   343  		if os.IsNotExist(err) {
   344  			return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
   345  		}
   346  		return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
   347  	}
   348  
   349  	if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
   350  		return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
   351  	}
   352  
   353  	if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
   354  		return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
   355  	}
   356  
   357  	return absContextDir, relDockerfile, nil
   358  }
   359  
   360  // writeToFile copies from the given reader and writes it to a file with the
   361  // given filename.
   362  func writeToFile(r io.Reader, filename string) error {
   363  	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
   364  	if err != nil {
   365  		return fmt.Errorf("unable to create file: %v", err)
   366  	}
   367  	defer file.Close()
   368  
   369  	if _, err := io.Copy(file, r); err != nil {
   370  		return fmt.Errorf("unable to write file: %v", err)
   371  	}
   372  
   373  	return nil
   374  }
   375  
   376  // getContextFromReader will read the contents of the given reader as either a
   377  // Dockerfile or tar archive to be extracted to a temporary directory used as
   378  // the context directory. Returns the absolute path to the temporary context
   379  // directory, the relative path of the dockerfile in that context directory,
   380  // and a non-nil error on success.
   381  func getContextFromReader(r io.Reader, dockerfileName string) (absContextDir, relDockerfile string, err error) {
   382  	buf := bufio.NewReader(r)
   383  
   384  	magic, err := buf.Peek(archive.HeaderSize)
   385  	if err != nil && err != io.EOF {
   386  		return "", "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
   387  	}
   388  
   389  	if absContextDir, err = ioutil.TempDir("", "docker-build-context-"); err != nil {
   390  		return "", "", fmt.Errorf("unbale to create temporary context directory: %v", err)
   391  	}
   392  
   393  	defer func(d string) {
   394  		if err != nil {
   395  			os.RemoveAll(d)
   396  		}
   397  	}(absContextDir)
   398  
   399  	if !archive.IsArchive(magic) { // Input should be read as a Dockerfile.
   400  		// -f option has no meaning when we're reading it from stdin,
   401  		// so just use our default Dockerfile name
   402  		relDockerfile = api.DefaultDockerfileName
   403  
   404  		return absContextDir, relDockerfile, writeToFile(buf, filepath.Join(absContextDir, relDockerfile))
   405  	}
   406  
   407  	if err := archive.Untar(buf, absContextDir, nil); err != nil {
   408  		return "", "", fmt.Errorf("unable to extract stdin to temporary context directory: %v", err)
   409  	}
   410  
   411  	return getDockerfileRelPath(absContextDir, dockerfileName)
   412  }
   413  
   414  // getContextFromGitURL uses a Git URL as context for a `docker build`. The
   415  // git repo is cloned into a temporary directory used as the context directory.
   416  // Returns the absolute path to the temporary context directory, the relative
   417  // path of the dockerfile in that context directory, and a non-nil error on
   418  // success.
   419  func getContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
   420  	if absContextDir, err = gitutils.Clone(gitURL); err != nil {
   421  		return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
   422  	}
   423  
   424  	return getDockerfileRelPath(absContextDir, dockerfileName)
   425  }
   426  
   427  // getContextFromURL uses a remote URL as context for a `docker build`. The
   428  // remote resource is downloaded as either a Dockerfile or a context tar
   429  // archive and stored in a temporary directory used as the context directory.
   430  // Returns the absolute path to the temporary context directory, the relative
   431  // path of the dockerfile in that context directory, and a non-nil error on
   432  // success.
   433  func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
   434  	response, err := httputils.Download(remoteURL)
   435  	if err != nil {
   436  		return "", "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
   437  	}
   438  	defer response.Body.Close()
   439  	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true)
   440  
   441  	// Pass the response body through a progress reader.
   442  	progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
   443  
   444  	return getContextFromReader(progReader, dockerfileName)
   445  }
   446  
   447  // getContextFromLocalDir uses the given local directory as context for a
   448  // `docker build`. Returns the absolute path to the local context directory,
   449  // the relative path of the dockerfile in that context directory, and a non-nil
   450  // error on success.
   451  func getContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
   452  	// When using a local context directory, when the Dockerfile is specified
   453  	// with the `-f/--file` option then it is considered relative to the
   454  	// current directory and not the context directory.
   455  	if dockerfileName != "" {
   456  		if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
   457  			return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
   458  		}
   459  	}
   460  
   461  	return getDockerfileRelPath(localDir, dockerfileName)
   462  }
   463  
   464  var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
   465  
   466  type trustedDockerfile struct {
   467  	*os.File
   468  	size int64
   469  }
   470  
   471  func (td *trustedDockerfile) Close() error {
   472  	td.File.Close()
   473  	return os.Remove(td.File.Name())
   474  }
   475  
   476  // resolvedTag records the repository, tag, and resolved digest reference
   477  // from a Dockerfile rewrite.
   478  type resolvedTag struct {
   479  	digestRef reference.Canonical
   480  	tagRef    reference.NamedTagged
   481  }
   482  
   483  // rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
   484  // "FROM <image>" instructions to a digest reference. `translator` is a
   485  // function that takes a repository name and tag reference and returns a
   486  // trusted digest reference.
   487  func rewriteDockerfileFrom(dockerfileName string, translator func(reference.NamedTagged) (reference.Canonical, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
   488  	dockerfile, err := os.Open(dockerfileName)
   489  	if err != nil {
   490  		return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err)
   491  	}
   492  	defer dockerfile.Close()
   493  
   494  	scanner := bufio.NewScanner(dockerfile)
   495  
   496  	// Make a tempfile to store the rewritten Dockerfile.
   497  	tempFile, err := ioutil.TempFile("", "trusted-dockerfile-")
   498  	if err != nil {
   499  		return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err)
   500  	}
   501  
   502  	trustedFile := &trustedDockerfile{
   503  		File: tempFile,
   504  	}
   505  
   506  	defer func() {
   507  		if err != nil {
   508  			// Close the tempfile if there was an error during Notary lookups.
   509  			// Otherwise the caller should close it.
   510  			trustedFile.Close()
   511  		}
   512  	}()
   513  
   514  	// Scan the lines of the Dockerfile, looking for a "FROM" line.
   515  	for scanner.Scan() {
   516  		line := scanner.Text()
   517  
   518  		matches := dockerfileFromLinePattern.FindStringSubmatch(line)
   519  		if matches != nil && matches[1] != "scratch" {
   520  			// Replace the line with a resolved "FROM repo@digest"
   521  			ref, err := reference.ParseNamed(matches[1])
   522  			if err != nil {
   523  				return nil, nil, err
   524  			}
   525  			ref = reference.WithDefaultTag(ref)
   526  			if ref, ok := ref.(reference.NamedTagged); ok && isTrusted() {
   527  				trustedRef, err := translator(ref)
   528  				if err != nil {
   529  					return nil, nil, err
   530  				}
   531  
   532  				line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.String()))
   533  				resolvedTags = append(resolvedTags, &resolvedTag{
   534  					digestRef: trustedRef,
   535  					tagRef:    ref,
   536  				})
   537  			}
   538  		}
   539  
   540  		n, err := fmt.Fprintln(tempFile, line)
   541  		if err != nil {
   542  			return nil, nil, err
   543  		}
   544  
   545  		trustedFile.size += int64(n)
   546  	}
   547  
   548  	tempFile.Seek(0, os.SEEK_SET)
   549  
   550  	return trustedFile, resolvedTags, scanner.Err()
   551  }
   552  
   553  // replaceDockerfileTarWrapper wraps the given input tar archive stream and
   554  // replaces the entry with the given Dockerfile name with the contents of the
   555  // new Dockerfile. Returns a new tar archive stream with the replaced
   556  // Dockerfile.
   557  func replaceDockerfileTarWrapper(inputTarStream io.ReadCloser, newDockerfile *trustedDockerfile, dockerfileName string) io.ReadCloser {
   558  	pipeReader, pipeWriter := io.Pipe()
   559  
   560  	go func() {
   561  		tarReader := tar.NewReader(inputTarStream)
   562  		tarWriter := tar.NewWriter(pipeWriter)
   563  
   564  		defer inputTarStream.Close()
   565  
   566  		for {
   567  			hdr, err := tarReader.Next()
   568  			if err == io.EOF {
   569  				// Signals end of archive.
   570  				tarWriter.Close()
   571  				pipeWriter.Close()
   572  				return
   573  			}
   574  			if err != nil {
   575  				pipeWriter.CloseWithError(err)
   576  				return
   577  			}
   578  
   579  			var content io.Reader = tarReader
   580  
   581  			if hdr.Name == dockerfileName {
   582  				// This entry is the Dockerfile. Since the tar archive was
   583  				// generated from a directory on the local filesystem, the
   584  				// Dockerfile will only appear once in the archive.
   585  				hdr.Size = newDockerfile.size
   586  				content = newDockerfile
   587  			}
   588  
   589  			if err := tarWriter.WriteHeader(hdr); err != nil {
   590  				pipeWriter.CloseWithError(err)
   591  				return
   592  			}
   593  
   594  			if _, err := io.Copy(tarWriter, content); err != nil {
   595  				pipeWriter.CloseWithError(err)
   596  				return
   597  			}
   598  		}
   599  	}()
   600  
   601  	return pipeReader
   602  }