2017-03-07 04:39:22 +08:00
|
|
|
package buildah
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2017-03-08 02:26:17 +08:00
|
|
|
"net/url"
|
2017-03-07 04:39:22 +08:00
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2017-07-17 22:42:58 +08:00
|
|
|
"syscall"
|
2017-03-28 15:01:59 +08:00
|
|
|
"time"
|
2017-03-07 04:39:22 +08:00
|
|
|
|
2019-03-25 18:23:56 +08:00
|
|
|
"github.com/containers/buildah/pkg/chrootuser"
|
2018-09-18 03:20:16 +08:00
|
|
|
"github.com/containers/buildah/util"
|
2017-03-07 04:39:22 +08:00
|
|
|
"github.com/containers/storage/pkg/archive"
|
2019-05-30 07:56:28 +08:00
|
|
|
"github.com/containers/storage/pkg/fileutils"
|
2018-03-17 05:19:29 +08:00
|
|
|
"github.com/containers/storage/pkg/idtools"
|
2019-05-09 03:59:27 +08:00
|
|
|
"github.com/containers/storage/pkg/system"
|
2017-11-30 22:34:02 +08:00
|
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
2017-06-02 03:23:02 +08:00
|
|
|
"github.com/pkg/errors"
|
2017-10-10 03:05:56 +08:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-03-07 04:39:22 +08:00
|
|
|
)
|
|
|
|
|
2018-06-08 22:52:52 +08:00
|
|
|
// AddAndCopyOptions holds options for add and copy commands.
|
2017-11-30 22:34:02 +08:00
|
|
|
type AddAndCopyOptions struct {
|
2018-06-08 22:52:52 +08:00
|
|
|
// Chown is a spec for the user who should be given ownership over the
|
|
|
|
// newly-added content, potentially overriding permissions which would
|
|
|
|
// otherwise match those of local files and directories being copied.
|
2017-11-30 22:34:02 +08:00
|
|
|
Chown string
|
2018-06-08 22:52:52 +08:00
|
|
|
// All of the data being copied will pass through Hasher, if set.
|
|
|
|
// If the sources are URLs or files, their contents will be passed to
|
|
|
|
// Hasher.
|
|
|
|
// If the sources include directory trees, Hasher will be passed
|
|
|
|
// tar-format archives of the directory trees.
|
|
|
|
Hasher io.Writer
|
2019-05-30 12:22:53 +08:00
|
|
|
// Excludes is the contents of the .dockerignore file
|
2019-03-20 02:28:54 +08:00
|
|
|
Excludes []string
|
2019-05-30 12:22:53 +08:00
|
|
|
// The base directory for Excludes and data to copy in
|
2019-03-20 02:28:54 +08:00
|
|
|
ContextDir string
|
2019-05-30 12:22:53 +08:00
|
|
|
// ID mapping options to use when contents to be copied are part of
|
|
|
|
// another container, and need ownerships to be mapped from the host to
|
|
|
|
// that container's values before copying them into the container.
|
|
|
|
IDMappingOptions *IDMappingOptions
|
2017-11-30 22:34:02 +08:00
|
|
|
}
|
|
|
|
|
2017-04-04 01:43:34 +08:00
|
|
|
// addURL copies the contents of the source URL to the destination. This is
|
2017-03-07 04:39:22 +08:00
|
|
|
// its own function so that deferred closes happen after we're done pulling
|
|
|
|
// down each item of potentially many.
|
2018-06-08 22:52:52 +08:00
|
|
|
func addURL(destination, srcurl string, owner idtools.IDPair, hasher io.Writer) error {
|
2017-03-07 04:39:22 +08:00
|
|
|
logrus.Debugf("saving %q to %q", srcurl, destination)
|
|
|
|
resp, err := http.Get(srcurl)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error getting %q", srcurl)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
f, err := os.Create(destination)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error creating %q", destination)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2018-03-17 05:19:29 +08:00
|
|
|
if err = f.Chown(owner.UID, owner.GID); err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return errors.Wrapf(err, "error setting owner of %q to %d:%d", destination, owner.UID, owner.GID)
|
2018-03-17 05:19:29 +08:00
|
|
|
}
|
2017-03-28 15:01:59 +08:00
|
|
|
if last := resp.Header.Get("Last-Modified"); last != "" {
|
2017-04-04 05:44:23 +08:00
|
|
|
if mtime, err2 := time.Parse(time.RFC1123, last); err2 != nil {
|
|
|
|
logrus.Debugf("error parsing Last-Modified time %q: %v", last, err2)
|
2017-03-28 15:01:59 +08:00
|
|
|
} else {
|
|
|
|
defer func() {
|
2017-04-04 05:44:23 +08:00
|
|
|
if err3 := os.Chtimes(destination, time.Now(), mtime); err3 != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
logrus.Debugf("error setting mtime on %q to Last-Modified time %q: %v", destination, last, err3)
|
2017-03-28 15:01:59 +08:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
defer f.Close()
|
2018-06-08 22:52:52 +08:00
|
|
|
bodyReader := io.Reader(resp.Body)
|
|
|
|
if hasher != nil {
|
|
|
|
bodyReader = io.TeeReader(bodyReader, hasher)
|
|
|
|
}
|
|
|
|
n, err := io.Copy(f, bodyReader)
|
2017-04-04 05:44:23 +08:00
|
|
|
if err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return errors.Wrapf(err, "error reading contents for %q from %q", destination, srcurl)
|
2017-04-04 05:44:23 +08:00
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
if resp.ContentLength >= 0 && n != resp.ContentLength {
|
2018-10-03 22:05:46 +08:00
|
|
|
return errors.Errorf("error reading contents for %q from %q: wrong length (%d != %d)", destination, srcurl, n, resp.ContentLength)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-28 15:01:59 +08:00
|
|
|
if err := f.Chmod(0600); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error setting permissions on %q", destination)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-24 01:48:23 +08:00
|
|
|
// Add copies the contents of the specified sources into the container's root
|
|
|
|
// filesystem, optionally extracting contents of local files that look like
|
|
|
|
// non-empty archives.
|
2017-11-30 22:34:02 +08:00
|
|
|
func (b *Builder) Add(destination string, extract bool, options AddAndCopyOptions, source ...string) error {
|
2019-05-30 07:56:28 +08:00
|
|
|
excludes, err := dockerIgnoreMatcher(options.Excludes, options.ContextDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-11-22 21:57:31 +08:00
|
|
|
mountPoint, err := b.Mount(b.MountLabel)
|
2017-03-24 01:48:23 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-24 01:48:23 +08:00
|
|
|
defer func() {
|
|
|
|
if err2 := b.Unmount(); err2 != nil {
|
|
|
|
logrus.Errorf("error unmounting container: %v", err2)
|
|
|
|
}
|
|
|
|
}()
|
2018-01-12 18:54:30 +08:00
|
|
|
// Find out which user (and group) the destination should belong to.
|
2019-05-16 06:40:15 +08:00
|
|
|
user, _, err := b.user(mountPoint, options.Chown)
|
2018-01-12 18:54:30 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-17 05:19:29 +08:00
|
|
|
containerOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
|
2018-06-07 23:25:47 +08:00
|
|
|
hostUID, hostGID, err := util.GetHostIDs(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap, user.UID, user.GID)
|
2018-03-17 05:19:29 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
|
2017-03-24 01:48:23 +08:00
|
|
|
dest := mountPoint
|
2017-03-07 04:39:22 +08:00
|
|
|
if destination != "" && filepath.IsAbs(destination) {
|
2019-05-26 20:56:13 +08:00
|
|
|
dir := filepath.Dir(destination)
|
|
|
|
if dir != "." && dir != "/" {
|
|
|
|
if err = idtools.MkdirAllAndChownNew(filepath.Join(dest, dir), 0755, hostOwner); err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating directory %q", filepath.Join(dest, dir))
|
|
|
|
}
|
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
dest = filepath.Join(dest, destination)
|
|
|
|
} else {
|
2018-03-17 05:19:29 +08:00
|
|
|
if err = idtools.MkdirAllAndChownNew(filepath.Join(dest, b.WorkDir()), 0755, hostOwner); err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return errors.Wrapf(err, "error creating directory %q", filepath.Join(dest, b.WorkDir()))
|
2017-03-24 01:47:07 +08:00
|
|
|
}
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
dest = filepath.Join(dest, b.WorkDir(), destination)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-28 03:35:09 +08:00
|
|
|
// If the destination was explicitly marked as a directory by ending it
|
|
|
|
// with a '/', create it so that we can be sure that it's a directory,
|
|
|
|
// and any files we're copying will be placed in the directory.
|
|
|
|
if len(destination) > 0 && destination[len(destination)-1] == os.PathSeparator {
|
2018-03-17 05:19:29 +08:00
|
|
|
if err = idtools.MkdirAllAndChownNew(dest, 0755, hostOwner); err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return errors.Wrapf(err, "error creating directory %q", dest)
|
2017-03-28 03:35:09 +08:00
|
|
|
}
|
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
// Make sure the destination's parent directory is usable.
|
2017-05-24 03:00:57 +08:00
|
|
|
if destpfi, err2 := os.Stat(filepath.Dir(dest)); err2 == nil && !destpfi.IsDir() {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("%q already exists, but is not a subdirectory)", filepath.Dir(dest))
|
2017-03-08 02:26:17 +08:00
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
// Now look at the destination itself.
|
|
|
|
destfi, err := os.Stat(dest)
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsNotExist(err) {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "couldn't determine what %q is", dest)
|
2017-03-24 01:49:38 +08:00
|
|
|
}
|
|
|
|
destfi = nil
|
|
|
|
}
|
2017-05-24 03:00:57 +08:00
|
|
|
if len(source) > 1 && (destfi == nil || !destfi.IsDir()) {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("destination %q is not a directory", dest)
|
2017-03-08 02:26:17 +08:00
|
|
|
}
|
2019-05-30 12:22:53 +08:00
|
|
|
copyFileWithTar := b.copyFileWithTar(options.IDMappingOptions, &containerOwner, options.Hasher)
|
|
|
|
copyWithTar := b.copyWithTar(options.IDMappingOptions, &containerOwner, options.Hasher)
|
2018-06-08 22:52:52 +08:00
|
|
|
untarPath := b.untarPath(nil, options.Hasher)
|
2019-03-20 02:28:54 +08:00
|
|
|
err = addHelper(excludes, extract, dest, destfi, hostOwner, options, copyFileWithTar, copyWithTar, untarPath, source...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// user returns the user (and group) information which the destination should belong to.
|
2019-05-16 06:40:15 +08:00
|
|
|
func (b *Builder) user(mountPoint string, userspec string) (specs.User, string, error) {
|
2019-03-20 02:28:54 +08:00
|
|
|
if userspec == "" {
|
|
|
|
userspec = b.User()
|
|
|
|
}
|
|
|
|
|
2019-05-16 06:40:15 +08:00
|
|
|
uid, gid, homeDir, err := chrootuser.GetUser(mountPoint, userspec)
|
2019-03-20 02:28:54 +08:00
|
|
|
u := specs.User{
|
|
|
|
UID: uid,
|
|
|
|
GID: gid,
|
|
|
|
Username: userspec,
|
|
|
|
}
|
|
|
|
if !strings.Contains(userspec, ":") {
|
|
|
|
groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
|
|
|
|
if err2 != nil {
|
|
|
|
if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
|
|
|
|
err = err2
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
u.AdditionalGids = groups
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-05-16 06:40:15 +08:00
|
|
|
return u, homeDir, err
|
2019-03-20 02:28:54 +08:00
|
|
|
}
|
|
|
|
|
2019-05-30 07:56:28 +08:00
|
|
|
// dockerIgnoreMatcher returns a matcher based on the contents of the .dockerignore file under contextDir
|
|
|
|
func dockerIgnoreMatcher(lines []string, contextDir string) (*fileutils.PatternMatcher, error) {
|
|
|
|
// if there's no context dir, there's no .dockerignore file to consult
|
|
|
|
if contextDir == "" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
patterns := []string{".dockerignore"}
|
|
|
|
for _, ignoreSpec := range lines {
|
|
|
|
ignoreSpec = strings.TrimSpace(ignoreSpec)
|
|
|
|
// ignore comments passed back from .dockerignore
|
|
|
|
if ignoreSpec == "" || ignoreSpec[0] == '#' {
|
2019-03-20 02:28:54 +08:00
|
|
|
continue
|
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
// if the spec starts with '!' it means the pattern
|
|
|
|
// should be included. make a note so that we can move
|
|
|
|
// it to the front of the updated pattern
|
|
|
|
includeFlag := ""
|
|
|
|
if strings.HasPrefix(ignoreSpec, "!") {
|
|
|
|
includeFlag = "!"
|
|
|
|
ignoreSpec = ignoreSpec[1:]
|
2019-03-20 02:28:54 +08:00
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
if ignoreSpec == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
patterns = append(patterns, includeFlag+filepath.Join(contextDir, ignoreSpec))
|
2019-03-20 02:28:54 +08:00
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
// if there are no patterns, save time by not constructing the object
|
|
|
|
if len(patterns) == 0 {
|
|
|
|
return nil, nil
|
2019-03-20 02:28:54 +08:00
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
// return a matcher object
|
|
|
|
matcher, err := fileutils.NewPatternMatcher(patterns)
|
2019-04-04 04:32:12 +08:00
|
|
|
if err != nil {
|
2019-05-30 07:56:28 +08:00
|
|
|
return nil, errors.Wrapf(err, "error creating file matcher using patterns %v", patterns)
|
2019-04-04 04:32:12 +08:00
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
return matcher, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func addHelper(excludes *fileutils.PatternMatcher, extract bool, dest string, destfi os.FileInfo, hostOwner idtools.IDPair, options AddAndCopyOptions, copyFileWithTar, copyWithTar, untarPath func(src, dest string) error, source ...string) error {
|
2017-03-07 04:39:22 +08:00
|
|
|
for _, src := range source {
|
|
|
|
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
|
|
|
|
// We assume that source is a file, and we're copying
|
2017-03-28 15:02:41 +08:00
|
|
|
// it to the destination. If the destination is
|
|
|
|
// already a directory, create a file inside of it.
|
|
|
|
// Otherwise, the destination is the file to which
|
|
|
|
// we'll save the contents.
|
2017-03-08 02:26:17 +08:00
|
|
|
url, err := url.Parse(src)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error parsing URL %q", src)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
d := dest
|
2017-05-24 03:00:57 +08:00
|
|
|
if destfi != nil && destfi.IsDir() {
|
2017-03-24 01:49:38 +08:00
|
|
|
d = filepath.Join(dest, path.Base(url.Path))
|
|
|
|
}
|
2018-10-03 22:05:46 +08:00
|
|
|
if err = addURL(d, src, hostOwner, options.Hasher); err != nil {
|
2017-11-30 22:34:02 +08:00
|
|
|
return err
|
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
continue
|
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
|
|
|
|
glob, err := filepath.Glob(src)
|
2017-03-07 04:39:22 +08:00
|
|
|
if err != nil {
|
2017-07-17 22:42:58 +08:00
|
|
|
return errors.Wrapf(err, "invalid glob %q", src)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
if len(glob) == 0 {
|
|
|
|
return errors.Wrapf(syscall.ENOENT, "no files found matching %q", src)
|
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
|
2017-07-17 22:42:58 +08:00
|
|
|
for _, gsrc := range glob {
|
2018-09-12 21:28:38 +08:00
|
|
|
esrc, err := filepath.EvalSymlinks(gsrc)
|
2017-07-17 22:42:58 +08:00
|
|
|
if err != nil {
|
2018-09-12 21:28:38 +08:00
|
|
|
return errors.Wrapf(err, "error evaluating symlinks %q", gsrc)
|
|
|
|
}
|
|
|
|
srcfi, err := os.Stat(esrc)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error reading %q", esrc)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
if srcfi.IsDir() {
|
|
|
|
// The source is a directory, so copy the contents of
|
|
|
|
// the source directory into the target directory. Try
|
|
|
|
// to create it first, so that if there's a problem,
|
|
|
|
// we'll discover why that won't work.
|
2018-03-17 05:19:29 +08:00
|
|
|
if err = idtools.MkdirAllAndChownNew(dest, 0755, hostOwner); err != nil {
|
2018-10-03 22:05:46 +08:00
|
|
|
return errors.Wrapf(err, "error creating directory %q", dest)
|
2017-07-17 22:42:58 +08:00
|
|
|
}
|
2018-09-12 21:28:38 +08:00
|
|
|
logrus.Debugf("copying %q to %q", esrc+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*")
|
2019-07-12 16:55:01 +08:00
|
|
|
if excludes == nil || !excludes.Exclusions() {
|
2019-03-20 02:28:54 +08:00
|
|
|
if err = copyWithTar(esrc, dest); err != nil {
|
|
|
|
return errors.Wrapf(err, "error copying %q to %q", esrc, dest)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err := filepath.Walk(esrc, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
skip, err := excludes.Matches(path)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error checking if %s is an excluded path", path)
|
|
|
|
}
|
|
|
|
if skip {
|
|
|
|
return nil
|
2019-03-20 02:28:54 +08:00
|
|
|
}
|
|
|
|
// combine the filename with the dest directory
|
2019-05-09 03:59:27 +08:00
|
|
|
fpath, err := filepath.Rel(esrc, path)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error converting %s to a path relative to %s", path, esrc)
|
|
|
|
}
|
|
|
|
mtime := info.ModTime()
|
|
|
|
atime := mtime
|
|
|
|
times := []syscall.Timespec{
|
2019-06-01 09:55:36 +08:00
|
|
|
syscall.NsecToTimespec(atime.Unix()),
|
|
|
|
syscall.NsecToTimespec(mtime.Unix()),
|
2019-05-09 03:59:27 +08:00
|
|
|
}
|
|
|
|
if info.IsDir() {
|
|
|
|
return addHelperDirectory(esrc, path, filepath.Join(dest, fpath), info, hostOwner, times)
|
|
|
|
}
|
|
|
|
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
2019-07-16 16:47:48 +08:00
|
|
|
return addHelperSymlink(path, filepath.Join(dest, fpath), hostOwner, times)
|
2019-05-09 03:59:27 +08:00
|
|
|
}
|
|
|
|
if !info.Mode().IsRegular() {
|
|
|
|
return errors.Errorf("error copying %q to %q: source is not a regular file; file mode is %s", path, dest, info.Mode())
|
|
|
|
}
|
2019-03-20 02:28:54 +08:00
|
|
|
if err = copyFileWithTar(path, filepath.Join(dest, fpath)); err != nil {
|
|
|
|
return errors.Wrapf(err, "error copying %q to %q", path, dest)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-07-17 22:42:58 +08:00
|
|
|
}
|
|
|
|
continue
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2019-03-20 02:28:54 +08:00
|
|
|
|
2018-09-12 21:28:38 +08:00
|
|
|
if !extract || !archive.IsArchivePath(esrc) {
|
2017-07-17 22:42:58 +08:00
|
|
|
// This source is a file, and either it's not an
|
|
|
|
// archive, or we don't care whether or not it's an
|
|
|
|
// archive.
|
|
|
|
d := dest
|
|
|
|
if destfi != nil && destfi.IsDir() {
|
|
|
|
d = filepath.Join(dest, filepath.Base(gsrc))
|
|
|
|
}
|
|
|
|
// Copy the file, preserving attributes.
|
2018-09-12 21:28:38 +08:00
|
|
|
logrus.Debugf("copying %q to %q", esrc, d)
|
2018-10-03 22:05:46 +08:00
|
|
|
if err = copyFileWithTar(esrc, d); err != nil {
|
2018-09-12 21:28:38 +08:00
|
|
|
return errors.Wrapf(err, "error copying %q to %q", esrc, d)
|
2017-07-17 22:42:58 +08:00
|
|
|
}
|
|
|
|
continue
|
2017-03-24 01:49:38 +08:00
|
|
|
}
|
2019-05-30 07:56:28 +08:00
|
|
|
|
2017-07-17 22:42:58 +08:00
|
|
|
// We're extracting an archive into the destination directory.
|
2018-09-12 21:28:38 +08:00
|
|
|
logrus.Debugf("extracting contents of %q into %q", esrc, dest)
|
2018-10-03 22:05:46 +08:00
|
|
|
if err = untarPath(esrc, dest); err != nil {
|
2018-09-12 21:28:38 +08:00
|
|
|
return errors.Wrapf(err, "error extracting %q into %q", esrc, dest)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2019-04-04 04:32:12 +08:00
|
|
|
|
2019-05-09 03:59:27 +08:00
|
|
|
func addHelperDirectory(esrc, path, dest string, info os.FileInfo, hostOwner idtools.IDPair, times []syscall.Timespec) error {
|
|
|
|
if err := idtools.MkdirAllAndChownNew(dest, info.Mode().Perm(), hostOwner); err != nil {
|
|
|
|
// discard only EEXIST on the top directory, which would have been created earlier in the caller
|
|
|
|
if !os.IsExist(err) || path != esrc {
|
|
|
|
return errors.Errorf("error creating directory %q", dest)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := idtools.SafeLchown(dest, hostOwner.UID, hostOwner.GID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error setting owner of directory %q to %d:%d", dest, hostOwner.UID, hostOwner.GID)
|
|
|
|
}
|
|
|
|
if err := system.LUtimesNano(dest, times); err != nil {
|
|
|
|
return errors.Wrapf(err, "error setting dates on directory %q", dest)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-16 16:47:48 +08:00
|
|
|
func addHelperSymlink(src, dest string, hostOwner idtools.IDPair, times []syscall.Timespec) error {
|
2019-05-09 03:59:27 +08:00
|
|
|
linkContents, err := os.Readlink(src)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error reading contents of symbolic link at %q", src)
|
|
|
|
}
|
|
|
|
if err = os.Symlink(linkContents, dest); err != nil {
|
2019-05-29 22:00:48 +08:00
|
|
|
if !os.IsExist(err) {
|
|
|
|
return errors.Wrapf(err, "error creating symbolic link to %q at %q", linkContents, dest)
|
|
|
|
}
|
|
|
|
if err = os.RemoveAll(dest); err != nil {
|
|
|
|
return errors.Wrapf(err, "error clearing symbolic link target %q", dest)
|
|
|
|
}
|
|
|
|
if err = os.Symlink(linkContents, dest); err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating symbolic link to %q at %q (second try)", linkContents, dest)
|
|
|
|
}
|
2019-05-09 03:59:27 +08:00
|
|
|
}
|
|
|
|
if err = idtools.SafeLchown(dest, hostOwner.UID, hostOwner.GID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error setting owner of symbolic link %q to %d:%d", dest, hostOwner.UID, hostOwner.GID)
|
|
|
|
}
|
|
|
|
if err = system.LUtimesNano(dest, times); err != nil {
|
|
|
|
return errors.Wrapf(err, "error setting dates on symbolic link %q", dest)
|
|
|
|
}
|
|
|
|
logrus.Debugf("Symlink(%s, %s)", linkContents, dest)
|
|
|
|
return nil
|
|
|
|
}
|