github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/bindings/images/build.go (about)

     1  package images
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/gzip"
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"io/fs"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"path/filepath"
    16  	"regexp"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"github.com/containers/buildah/define"
    22  	"github.com/containers/image/v5/types"
    23  	"github.com/hanks177/podman/v4/pkg/auth"
    24  	"github.com/hanks177/podman/v4/pkg/bindings"
    25  	"github.com/hanks177/podman/v4/pkg/domain/entities"
    26  	"github.com/containers/storage/pkg/fileutils"
    27  	"github.com/containers/storage/pkg/ioutils"
    28  	"github.com/docker/go-units"
    29  	"github.com/hashicorp/go-multierror"
    30  	jsoniter "github.com/json-iterator/go"
    31  	"github.com/pkg/errors"
    32  	"github.com/sirupsen/logrus"
    33  )
    34  
    35  type devino struct {
    36  	Dev uint64
    37  	Ino uint64
    38  }
    39  
    40  var (
    41  	iidRegex = regexp.MustCompile(`^[0-9a-f]{12}`)
    42  )
    43  
    44  // Build creates an image using a containerfile reference
    45  func Build(ctx context.Context, containerFiles []string, options entities.BuildOptions) (*entities.BuildReport, error) {
    46  	if options.CommonBuildOpts == nil {
    47  		options.CommonBuildOpts = new(define.CommonBuildOptions)
    48  	}
    49  
    50  	params := url.Values{}
    51  
    52  	if caps := options.AddCapabilities; len(caps) > 0 {
    53  		c, err := jsoniter.MarshalToString(caps)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  		params.Add("addcaps", c)
    58  	}
    59  
    60  	if annotations := options.Annotations; len(annotations) > 0 {
    61  		l, err := jsoniter.MarshalToString(annotations)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  		params.Set("annotations", l)
    66  	}
    67  
    68  	if cppflags := options.CPPFlags; len(cppflags) > 0 {
    69  		l, err := jsoniter.MarshalToString(cppflags)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  		params.Set("cppflags", l)
    74  	}
    75  
    76  	if options.AllPlatforms {
    77  		params.Add("allplatforms", "1")
    78  	}
    79  
    80  	params.Add("t", options.Output)
    81  	for _, tag := range options.AdditionalTags {
    82  		params.Add("t", tag)
    83  	}
    84  	if additionalBuildContexts := options.AdditionalBuildContexts; len(additionalBuildContexts) > 0 {
    85  		additionalBuildContextMap, err := jsoniter.Marshal(additionalBuildContexts)
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  		params.Set("additionalbuildcontexts", string(additionalBuildContextMap))
    90  	}
    91  	if buildArgs := options.Args; len(buildArgs) > 0 {
    92  		bArgs, err := jsoniter.MarshalToString(buildArgs)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		params.Set("buildargs", bArgs)
    97  	}
    98  	if excludes := options.Excludes; len(excludes) > 0 {
    99  		bArgs, err := jsoniter.MarshalToString(excludes)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		params.Set("excludes", bArgs)
   104  	}
   105  	if cpuPeriod := options.CommonBuildOpts.CPUPeriod; cpuPeriod > 0 {
   106  		params.Set("cpuperiod", strconv.Itoa(int(cpuPeriod)))
   107  	}
   108  	if cpuQuota := options.CommonBuildOpts.CPUQuota; cpuQuota > 0 {
   109  		params.Set("cpuquota", strconv.Itoa(int(cpuQuota)))
   110  	}
   111  	if cpuSetCpus := options.CommonBuildOpts.CPUSetCPUs; len(cpuSetCpus) > 0 {
   112  		params.Set("cpusetcpus", cpuSetCpus)
   113  	}
   114  	if cpuSetMems := options.CommonBuildOpts.CPUSetMems; len(cpuSetMems) > 0 {
   115  		params.Set("cpusetmems", cpuSetMems)
   116  	}
   117  	if cpuShares := options.CommonBuildOpts.CPUShares; cpuShares > 0 {
   118  		params.Set("cpushares", strconv.Itoa(int(cpuShares)))
   119  	}
   120  	if len(options.CommonBuildOpts.CgroupParent) > 0 {
   121  		params.Set("cgroupparent", options.CommonBuildOpts.CgroupParent)
   122  	}
   123  
   124  	params.Set("networkmode", strconv.Itoa(int(options.ConfigureNetwork)))
   125  	params.Set("outputformat", options.OutputFormat)
   126  
   127  	if devices := options.Devices; len(devices) > 0 {
   128  		d, err := jsoniter.MarshalToString(devices)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		params.Add("devices", d)
   133  	}
   134  
   135  	if dnsservers := options.CommonBuildOpts.DNSServers; len(dnsservers) > 0 {
   136  		c, err := jsoniter.MarshalToString(dnsservers)
   137  		if err != nil {
   138  			return nil, err
   139  		}
   140  		params.Add("dnsservers", c)
   141  	}
   142  	if dnsoptions := options.CommonBuildOpts.DNSOptions; len(dnsoptions) > 0 {
   143  		c, err := jsoniter.MarshalToString(dnsoptions)
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  		params.Add("dnsoptions", c)
   148  	}
   149  	if dnssearch := options.CommonBuildOpts.DNSSearch; len(dnssearch) > 0 {
   150  		c, err := jsoniter.MarshalToString(dnssearch)
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		params.Add("dnssearch", c)
   155  	}
   156  
   157  	if caps := options.DropCapabilities; len(caps) > 0 {
   158  		c, err := jsoniter.MarshalToString(caps)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		params.Add("dropcaps", c)
   163  	}
   164  
   165  	if options.ForceRmIntermediateCtrs {
   166  		params.Set("forcerm", "1")
   167  	}
   168  	if options.RemoveIntermediateCtrs {
   169  		params.Set("rm", "1")
   170  	} else {
   171  		params.Set("rm", "0")
   172  	}
   173  	if len(options.From) > 0 {
   174  		params.Set("from", options.From)
   175  	}
   176  	if options.IgnoreUnrecognizedInstructions {
   177  		params.Set("ignore", "1")
   178  	}
   179  	params.Set("isolation", strconv.Itoa(int(options.Isolation)))
   180  	if options.CommonBuildOpts.HTTPProxy {
   181  		params.Set("httpproxy", "1")
   182  	}
   183  	if options.Jobs != nil {
   184  		params.Set("jobs", strconv.FormatUint(uint64(*options.Jobs), 10))
   185  	}
   186  	if labels := options.Labels; len(labels) > 0 {
   187  		l, err := jsoniter.MarshalToString(labels)
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  		params.Set("labels", l)
   192  	}
   193  
   194  	if opt := options.CommonBuildOpts.LabelOpts; len(opt) > 0 {
   195  		o, err := jsoniter.MarshalToString(opt)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  		params.Set("labelopts", o)
   200  	}
   201  
   202  	if len(options.CommonBuildOpts.SeccompProfilePath) > 0 {
   203  		params.Set("seccomp", options.CommonBuildOpts.SeccompProfilePath)
   204  	}
   205  
   206  	if len(options.CommonBuildOpts.ApparmorProfile) > 0 {
   207  		params.Set("apparmor", options.CommonBuildOpts.ApparmorProfile)
   208  	}
   209  
   210  	if options.Layers {
   211  		params.Set("layers", "1")
   212  	}
   213  	if options.LogRusage {
   214  		params.Set("rusage", "1")
   215  	}
   216  	if len(options.RusageLogFile) > 0 {
   217  		params.Set("rusagelogfile", options.RusageLogFile)
   218  	}
   219  	if len(options.Manifest) > 0 {
   220  		params.Set("manifest", options.Manifest)
   221  	}
   222  	if memSwap := options.CommonBuildOpts.MemorySwap; memSwap > 0 {
   223  		params.Set("memswap", strconv.Itoa(int(memSwap)))
   224  	}
   225  	if mem := options.CommonBuildOpts.Memory; mem > 0 {
   226  		params.Set("memory", strconv.Itoa(int(mem)))
   227  	}
   228  	if options.NoCache {
   229  		params.Set("nocache", "1")
   230  	}
   231  	if t := options.Output; len(t) > 0 {
   232  		params.Set("output", t)
   233  	}
   234  	if t := options.OSVersion; len(t) > 0 {
   235  		params.Set("osversion", t)
   236  	}
   237  	for _, t := range options.OSFeatures {
   238  		params.Set("osfeature", t)
   239  	}
   240  	var platform string
   241  	if len(options.OS) > 0 {
   242  		platform = options.OS
   243  	}
   244  	if len(options.Architecture) > 0 {
   245  		if len(platform) == 0 {
   246  			platform = "linux"
   247  		}
   248  		platform += "/" + options.Architecture
   249  	} else if len(platform) > 0 {
   250  		platform += "/" + runtime.GOARCH
   251  	}
   252  	if len(platform) > 0 {
   253  		params.Set("platform", platform)
   254  	}
   255  	if len(options.Platforms) > 0 {
   256  		params.Del("platform")
   257  		for _, platformSpec := range options.Platforms {
   258  			platform = platformSpec.OS + "/" + platformSpec.Arch
   259  			if platformSpec.Variant != "" {
   260  				platform += "/" + platformSpec.Variant
   261  			}
   262  			params.Add("platform", platform)
   263  		}
   264  	}
   265  	var err error
   266  	var contextDir string
   267  	if contextDir, err = filepath.EvalSymlinks(options.ContextDirectory); err == nil {
   268  		options.ContextDirectory = contextDir
   269  	}
   270  
   271  	params.Set("pullpolicy", options.PullPolicy.String())
   272  
   273  	switch options.CommonBuildOpts.IdentityLabel {
   274  	case types.OptionalBoolTrue:
   275  		params.Set("identitylabel", "1")
   276  	case types.OptionalBoolFalse:
   277  		params.Set("identitylabel", "0")
   278  	}
   279  	if options.Quiet {
   280  		params.Set("q", "1")
   281  	}
   282  	if options.RemoveIntermediateCtrs {
   283  		params.Set("rm", "1")
   284  	}
   285  	if len(options.Target) > 0 {
   286  		params.Set("target", options.Target)
   287  	}
   288  
   289  	if hosts := options.CommonBuildOpts.AddHost; len(hosts) > 0 {
   290  		h, err := jsoniter.MarshalToString(hosts)
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  		params.Set("extrahosts", h)
   295  	}
   296  	if nsoptions := options.NamespaceOptions; len(nsoptions) > 0 {
   297  		ns, err := jsoniter.MarshalToString(nsoptions)
   298  		if err != nil {
   299  			return nil, err
   300  		}
   301  		params.Set("nsoptions", ns)
   302  	}
   303  	if shmSize := options.CommonBuildOpts.ShmSize; len(shmSize) > 0 {
   304  		shmBytes, err := units.RAMInBytes(shmSize)
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  		params.Set("shmsize", strconv.Itoa(int(shmBytes)))
   309  	}
   310  	if options.Squash {
   311  		params.Set("squash", "1")
   312  	}
   313  
   314  	if options.Timestamp != nil {
   315  		t := *options.Timestamp
   316  		params.Set("timestamp", strconv.FormatInt(t.Unix(), 10))
   317  	}
   318  
   319  	if len(options.CommonBuildOpts.Ulimit) > 0 {
   320  		ulimitsJSON, err := json.Marshal(options.CommonBuildOpts.Ulimit)
   321  		if err != nil {
   322  			return nil, err
   323  		}
   324  		params.Set("ulimits", string(ulimitsJSON))
   325  	}
   326  
   327  	for _, env := range options.Envs {
   328  		params.Add("setenv", env)
   329  	}
   330  
   331  	for _, uenv := range options.UnsetEnvs {
   332  		params.Add("unsetenv", uenv)
   333  	}
   334  
   335  	var (
   336  		headers http.Header
   337  	)
   338  	if options.SystemContext != nil {
   339  		if options.SystemContext.DockerAuthConfig != nil {
   340  			headers, err = auth.MakeXRegistryAuthHeader(options.SystemContext, options.SystemContext.DockerAuthConfig.Username, options.SystemContext.DockerAuthConfig.Password)
   341  		} else {
   342  			headers, err = auth.MakeXRegistryConfigHeader(options.SystemContext, "", "")
   343  		}
   344  		if options.SystemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolTrue {
   345  			params.Set("tlsVerify", "false")
   346  		}
   347  	}
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	stdout := io.Writer(os.Stdout)
   353  	if options.Out != nil {
   354  		stdout = options.Out
   355  	}
   356  
   357  	excludes := options.Excludes
   358  	if len(excludes) == 0 {
   359  		excludes, err = parseDockerignore(options.ContextDirectory)
   360  		if err != nil {
   361  			return nil, err
   362  		}
   363  	}
   364  
   365  	contextDir, err = filepath.Abs(options.ContextDirectory)
   366  	if err != nil {
   367  		logrus.Errorf("Cannot find absolute path of %v: %v", options.ContextDirectory, err)
   368  		return nil, err
   369  	}
   370  
   371  	tarContent := []string{options.ContextDirectory}
   372  	newContainerFiles := []string{} // dockerfile paths, relative to context dir, ToSlash()ed
   373  
   374  	dontexcludes := []string{"!Dockerfile", "!Containerfile", "!.dockerignore", "!.containerignore"}
   375  	for _, c := range containerFiles {
   376  		if c == "/dev/stdin" {
   377  			content, err := ioutil.ReadAll(os.Stdin)
   378  			if err != nil {
   379  				return nil, err
   380  			}
   381  			tmpFile, err := ioutil.TempFile("", "build")
   382  			if err != nil {
   383  				return nil, err
   384  			}
   385  			defer os.Remove(tmpFile.Name()) // clean up
   386  			defer tmpFile.Close()
   387  			if _, err := tmpFile.Write(content); err != nil {
   388  				return nil, err
   389  			}
   390  			c = tmpFile.Name()
   391  		}
   392  		c = filepath.Clean(c)
   393  		cfDir := filepath.Dir(c)
   394  		if absDir, err := filepath.EvalSymlinks(cfDir); err == nil {
   395  			name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator)))
   396  			c = filepath.Join(absDir, name)
   397  		}
   398  
   399  		containerfile, err := filepath.Abs(c)
   400  		if err != nil {
   401  			logrus.Errorf("Cannot find absolute path of %v: %v", c, err)
   402  			return nil, err
   403  		}
   404  
   405  		// Check if Containerfile is in the context directory, if so truncate the context directory off path
   406  		// Do NOT add to tarfile
   407  		if strings.HasPrefix(containerfile, contextDir+string(filepath.Separator)) {
   408  			containerfile = strings.TrimPrefix(containerfile, contextDir+string(filepath.Separator))
   409  			dontexcludes = append(dontexcludes, "!"+containerfile)
   410  		} else {
   411  			// If Containerfile does not exist, assume it is in context directory and do Not add to tarfile
   412  			if _, err := os.Lstat(containerfile); err != nil {
   413  				if !os.IsNotExist(err) {
   414  					return nil, err
   415  				}
   416  				containerfile = c
   417  			} else {
   418  				// If Containerfile does exist and not in the context directory, add it to the tarfile
   419  				tarContent = append(tarContent, containerfile)
   420  			}
   421  		}
   422  		newContainerFiles = append(newContainerFiles, filepath.ToSlash(containerfile))
   423  	}
   424  	if len(newContainerFiles) > 0 {
   425  		cFileJSON, err := json.Marshal(newContainerFiles)
   426  		if err != nil {
   427  			return nil, err
   428  		}
   429  		params.Set("dockerfile", string(cFileJSON))
   430  	}
   431  
   432  	// build secrets are usually absolute host path or relative to context dir on host
   433  	// in any case move secret to current context and ship the tar.
   434  	if secrets := options.CommonBuildOpts.Secrets; len(secrets) > 0 {
   435  		secretsForRemote := []string{}
   436  
   437  		for _, secret := range secrets {
   438  			secretOpt := strings.Split(secret, ",")
   439  			if len(secretOpt) > 0 {
   440  				modifiedOpt := []string{}
   441  				for _, token := range secretOpt {
   442  					arr := strings.SplitN(token, "=", 2)
   443  					if len(arr) > 1 {
   444  						if arr[0] == "src" {
   445  							// read specified secret into a tmp file
   446  							// move tmp file to tar and change secret source to relative tmp file
   447  							tmpSecretFile, err := ioutil.TempFile(options.ContextDirectory, "podman-build-secret")
   448  							if err != nil {
   449  								return nil, err
   450  							}
   451  							defer os.Remove(tmpSecretFile.Name()) // clean up
   452  							defer tmpSecretFile.Close()
   453  							srcSecretFile, err := os.Open(arr[1])
   454  							if err != nil {
   455  								return nil, err
   456  							}
   457  							defer srcSecretFile.Close()
   458  							_, err = io.Copy(tmpSecretFile, srcSecretFile)
   459  							if err != nil {
   460  								return nil, err
   461  							}
   462  
   463  							// add tmp file to context dir
   464  							tarContent = append(tarContent, tmpSecretFile.Name())
   465  
   466  							modifiedSrc := fmt.Sprintf("src=%s", filepath.Base(tmpSecretFile.Name()))
   467  							modifiedOpt = append(modifiedOpt, modifiedSrc)
   468  						} else {
   469  							modifiedOpt = append(modifiedOpt, token)
   470  						}
   471  					}
   472  				}
   473  				secretsForRemote = append(secretsForRemote, strings.Join(modifiedOpt, ","))
   474  			}
   475  		}
   476  
   477  		c, err := jsoniter.MarshalToString(secretsForRemote)
   478  		if err != nil {
   479  			return nil, err
   480  		}
   481  		params.Add("secrets", c)
   482  	}
   483  
   484  	tarfile, err := nTar(append(excludes, dontexcludes...), tarContent...)
   485  	if err != nil {
   486  		logrus.Errorf("Cannot tar container entries %v error: %v", tarContent, err)
   487  		return nil, err
   488  	}
   489  	defer func() {
   490  		if err := tarfile.Close(); err != nil {
   491  			logrus.Errorf("%v\n", err)
   492  		}
   493  	}()
   494  
   495  	conn, err := bindings.GetClient(ctx)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  	response, err := conn.DoRequest(ctx, tarfile, http.MethodPost, "/build", params, headers)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	defer response.Body.Close()
   504  
   505  	if !response.IsSuccess() {
   506  		return nil, response.Process(err)
   507  	}
   508  
   509  	body := response.Body.(io.Reader)
   510  	if logrus.IsLevelEnabled(logrus.DebugLevel) {
   511  		if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found {
   512  			if keep, _ := strconv.ParseBool(v); keep {
   513  				t, _ := ioutil.TempFile("", "build_*_client")
   514  				defer t.Close()
   515  				body = io.TeeReader(response.Body, t)
   516  			}
   517  		}
   518  	}
   519  
   520  	dec := json.NewDecoder(body)
   521  
   522  	var id string
   523  	for {
   524  		var s struct {
   525  			Stream string `json:"stream,omitempty"`
   526  			Error  string `json:"error,omitempty"`
   527  		}
   528  
   529  		select {
   530  		// FIXME(vrothberg): it seems we always hit the EOF case below,
   531  		// even when the server quit but it seems desirable to
   532  		// distinguish a proper build from a transient EOF.
   533  		case <-response.Request.Context().Done():
   534  			return &entities.BuildReport{ID: id}, nil
   535  		default:
   536  			// non-blocking select
   537  		}
   538  
   539  		if err := dec.Decode(&s); err != nil {
   540  			if errors.Is(err, io.ErrUnexpectedEOF) {
   541  				return nil, errors.Wrap(err, "server probably quit")
   542  			}
   543  			// EOF means the stream is over in which case we need
   544  			// to have read the id.
   545  			if errors.Is(err, io.EOF) && id != "" {
   546  				break
   547  			}
   548  			return &entities.BuildReport{ID: id}, errors.Wrap(err, "decoding stream")
   549  		}
   550  
   551  		switch {
   552  		case s.Stream != "":
   553  			raw := []byte(s.Stream)
   554  			stdout.Write(raw)
   555  			if iidRegex.Match(raw) {
   556  				id = strings.TrimSuffix(s.Stream, "\n")
   557  			}
   558  		case s.Error != "":
   559  			// If there's an error, return directly.  The stream
   560  			// will be closed on return.
   561  			return &entities.BuildReport{ID: id}, errors.New(s.Error)
   562  		default:
   563  			return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input")
   564  		}
   565  	}
   566  	return &entities.BuildReport{ID: id}, nil
   567  }
   568  
   569  func nTar(excludes []string, sources ...string) (io.ReadCloser, error) {
   570  	pm, err := fileutils.NewPatternMatcher(excludes)
   571  	if err != nil {
   572  		return nil, errors.Wrapf(err, "error processing excludes list %v", excludes)
   573  	}
   574  
   575  	if len(sources) == 0 {
   576  		return nil, errors.New("No source(s) provided for build")
   577  	}
   578  
   579  	pr, pw := io.Pipe()
   580  	gw := gzip.NewWriter(pw)
   581  	tw := tar.NewWriter(gw)
   582  
   583  	var merr *multierror.Error
   584  	go func() {
   585  		defer pw.Close()
   586  		defer gw.Close()
   587  		defer tw.Close()
   588  		seen := make(map[devino]string)
   589  		for _, src := range sources {
   590  			s, err := filepath.Abs(src)
   591  			if err != nil {
   592  				logrus.Errorf("Cannot stat one of source context: %v", err)
   593  				merr = multierror.Append(merr, err)
   594  				return
   595  			}
   596  			err = filepath.WalkDir(s, func(path string, d fs.DirEntry, err error) error {
   597  				if err != nil {
   598  					return err
   599  				}
   600  
   601  				// check if what we are given is an empty dir, if so then continue w/ it. Else return.
   602  				// if we are given a file or a symlink, we do not want to exclude it.
   603  				if d.IsDir() && s == path {
   604  					var p *os.File
   605  					p, err = os.Open(path)
   606  					if err != nil {
   607  						return err
   608  					}
   609  					defer p.Close()
   610  					_, err = p.Readdir(1)
   611  					if err != io.EOF {
   612  						return nil // non empty root dir, need to return
   613  					} else if err != nil {
   614  						logrus.Errorf("While reading directory %v: %v", path, err)
   615  					}
   616  				}
   617  				name := filepath.ToSlash(strings.TrimPrefix(path, s+string(filepath.Separator)))
   618  
   619  				excluded, err := pm.Matches(name) // nolint:staticcheck
   620  				if err != nil {
   621  					return errors.Wrapf(err, "error checking if %q is excluded", name)
   622  				}
   623  				if excluded {
   624  					// Note: filepath.SkipDir is not possible to use given .dockerignore semantics.
   625  					// An exception to exclusions may include an excluded directory, therefore we
   626  					// are required to visit all files. :(
   627  					return nil
   628  				}
   629  				switch {
   630  				case d.Type().IsRegular(): // add file item
   631  					info, err := d.Info()
   632  					if err != nil {
   633  						return err
   634  					}
   635  					di, isHardLink := checkHardLink(info)
   636  					if err != nil {
   637  						return err
   638  					}
   639  
   640  					hdr, err := tar.FileInfoHeader(info, "")
   641  					if err != nil {
   642  						return err
   643  					}
   644  					hdr.Uid, hdr.Gid = 0, 0
   645  					orig, ok := seen[di]
   646  					if ok {
   647  						hdr.Typeflag = tar.TypeLink
   648  						hdr.Linkname = orig
   649  						hdr.Size = 0
   650  						hdr.Name = name
   651  						return tw.WriteHeader(hdr)
   652  					}
   653  					f, err := os.Open(path)
   654  					if err != nil {
   655  						return err
   656  					}
   657  
   658  					hdr.Name = name
   659  					if err := tw.WriteHeader(hdr); err != nil {
   660  						f.Close()
   661  						return err
   662  					}
   663  
   664  					_, err = io.Copy(tw, f)
   665  					f.Close()
   666  					if err == nil && isHardLink {
   667  						seen[di] = name
   668  					}
   669  					return err
   670  				case d.IsDir(): // add folders
   671  					info, err := d.Info()
   672  					if err != nil {
   673  						return err
   674  					}
   675  					hdr, lerr := tar.FileInfoHeader(info, name)
   676  					if lerr != nil {
   677  						return lerr
   678  					}
   679  					hdr.Name = name
   680  					hdr.Uid, hdr.Gid = 0, 0
   681  					if lerr := tw.WriteHeader(hdr); lerr != nil {
   682  						return lerr
   683  					}
   684  				case d.Type()&os.ModeSymlink != 0: // add symlinks as it, not content
   685  					link, err := os.Readlink(path)
   686  					if err != nil {
   687  						return err
   688  					}
   689  					info, err := d.Info()
   690  					if err != nil {
   691  						return err
   692  					}
   693  					hdr, lerr := tar.FileInfoHeader(info, link)
   694  					if lerr != nil {
   695  						return lerr
   696  					}
   697  					hdr.Name = name
   698  					hdr.Uid, hdr.Gid = 0, 0
   699  					if lerr := tw.WriteHeader(hdr); lerr != nil {
   700  						return lerr
   701  					}
   702  				} // skip other than file,folder and symlinks
   703  				return nil
   704  			})
   705  			merr = multierror.Append(merr, err)
   706  		}
   707  	}()
   708  	rc := ioutils.NewReadCloserWrapper(pr, func() error {
   709  		if merr != nil {
   710  			merr = multierror.Append(merr, pr.Close())
   711  			return merr.ErrorOrNil()
   712  		}
   713  		return pr.Close()
   714  	})
   715  	return rc, nil
   716  }
   717  
   718  func parseDockerignore(root string) ([]string, error) {
   719  	ignore, err := ioutil.ReadFile(filepath.Join(root, ".containerignore"))
   720  	if err != nil {
   721  		var dockerIgnoreErr error
   722  		ignore, dockerIgnoreErr = ioutil.ReadFile(filepath.Join(root, ".dockerignore"))
   723  		if dockerIgnoreErr != nil && !os.IsNotExist(dockerIgnoreErr) {
   724  			return nil, errors.Wrapf(err, "error reading .containerignore: '%s'", root)
   725  		}
   726  	}
   727  	rawexcludes := strings.Split(string(ignore), "\n")
   728  	excludes := make([]string, 0, len(rawexcludes))
   729  	for _, e := range rawexcludes {
   730  		if len(e) == 0 || e[0] == '#' {
   731  			continue
   732  		}
   733  		excludes = append(excludes, e)
   734  	}
   735  	return excludes, nil
   736  }