Example #1
0
 def dest_realpath(self, unpack_path, dst):
     """Return the canonicalized version of path dst within (canonical) image
     path unpack_path. We can't use os.path.realpath() because if dst is
     an absolute symlink, we need to use the *image's* root directory, not
     the host. Thus, we have to resolve symlinks manually."""
     unpack_path = ch.Path(unpack_path)
     dst_canon = ch.Path(unpack_path)
     dst = ch.Path(dst)
     dst_parts = list(reversed(
         dst.parts))  # easier to operate on end of list
     iter_ct = 0
     while (len(dst_parts) > 0):
         iter_ct += 1
         if (iter_ct > 100):  # arbitrary
             ch.FATAL("can't COPY: too many path components")
         ch.TRACE("current destination: %d %s" % (iter_ct, dst_canon))
         #ch.TRACE("parts remaining: %s" % dst_parts)
         part = dst_parts.pop()
         if (part == "/" or part == "//"):  # 3 or more slashes yields "/"
             ch.TRACE("skipping root")
             continue
         cand = dst_canon // part
         ch.TRACE("checking: %s" % cand)
         if (not cand.is_symlink()):
             ch.TRACE("not symlink")
             dst_canon = cand
         else:
             target = ch.Path(os.readlink(cand))
             ch.TRACE("symlink to: %s" % target)
             assert (len(target.parts) > 0)  # POSIX says no empty symlinks
             if (target.is_absolute()):
                 ch.TRACE("absolute")
                 dst_canon = ch.Path(unpack_path)
             else:
                 ch.TRACE("relative")
             dst_parts.extend(reversed(target.parts))
     return dst_canon
Example #2
0
 def chdir(self, path):
     if (path.startswith("/")):
         self.workdir = ch.Path(path)
     else:
         self.workdir //= path
Example #3
0
 def workdir(self):
     return ch.Path(images[image_i].metadata["cwd"])
Example #4
0
 def execute_(self):
     if (cli.context == "-"):
         ch.FATAL("can't COPY: no context because \"-\" given")
     if (len(self.srcs) < 1):
         ch.FATAL("can't COPY: must specify at least one source")
     # Complain about unsupported stuff.
     if (self.options.pop("chown", False)):
         self.unsupported_forever_warn("--chown")
     # Any remaining options are invalid.
     self.options_assert_empty()
     # Find the context directory.
     if (self.from_ is None):
         context = cli.context
     else:
         if (self.from_ == image_i or self.from_ == image_alias):
             ch.FATAL("COPY --from: stage %s is the current stage" %
                      self.from_)
         if (not self.from_ in images):
             # FIXME: Would be nice to also report if a named stage is below.
             if (isinstance(self.from_, int) and self.from_ < image_ct):
                 if (self.from_ < 0):
                     ch.FATAL(
                         "COPY --from: invalid negative stage index %d" %
                         self.from_)
                 else:
                     ch.FATAL("COPY --from: stage %d does not exist yet" %
                              self.from_)
             else:
                 ch.FATAL("COPY --from: stage %s does not exist" %
                          self.from_)
         context = images[self.from_].unpack_path
     context_canon = os.path.realpath(context)
     ch.VERBOSE("context: %s" % context)
     # Expand source wildcards.
     srcs = list()
     for src in self.srcs:
         matches = glob.glob("%s/%s" %
                             (context, src))  # glob can't take Path
         if (len(matches) == 0):
             ch.FATAL("can't copy: not found: %s" % src)
         for i in matches:
             srcs.append(i)
             ch.VERBOSE("source: %s" % i)
     # Validate sources are within context directory. (Can't convert to
     # canonical paths yet because we need the source path as given.)
     for src in srcs:
         src_canon = os.path.realpath(src)
         if (not os.path.commonpath([src_canon, context_canon
                                     ]).startswith(context_canon)):
             ch.FATAL("can't COPY from outside context: %s" % src)
     # Locate the destination.
     unpack_canon = os.path.realpath(images[image_i].unpack_path)
     if (self.dst.startswith("/")):
         dst = ch.Path(self.dst)
     else:
         dst = env.workdir // self.dst
     ch.VERBOSE("destination, as given: %s" % dst)
     dst_canon = self.dest_realpath(unpack_canon,
                                    dst)  # strips trailing slash
     ch.VERBOSE("destination, canonical: %s" % dst_canon)
     if (not os.path.commonpath([dst_canon, unpack_canon
                                 ]).startswith(unpack_canon)):
         ch.FATAL("can't COPY: destination not in image: %s" % dst_canon)
     # Create the destination directory if needed.
     if (self.dst.endswith("/") or len(srcs) > 1 or os.path.isdir(srcs[0])):
         if (not os.path.exists(dst_canon)):
             ch.mkdirs(dst_canon)
         elif (not os.path.isdir(dst_canon)):  # not symlink b/c realpath()
             ch.FATAL("can't COPY: not a directory: %s" % dst_canon)
     # Copy each source.
     for src in srcs:
         if (os.path.isfile(src)):
             self.copy_src_file(src, dst_canon)
         elif (os.path.isdir(src)):
             self.copy_src_dir(src, dst_canon)
         else:
             ch.FATAL("can't COPY: unknown file type: %s" % src)
Example #5
0
    def copy_src_dir(self, src, dst):
        """Copy the contents of directory src, named by COPY, either explicitly
         or with wildcards, to dst. src might be a symlink, but dst is a
         canonical path. Both must be at the top level of the COPY
         instruction; i.e., this function must not be called recursively. dst
         must exist already and be a directory. Unlike subdirectories, the
         metadata of dst will not be altered to match src."""
        def onerror(x):
            ch.FATAL("can't scan directory: %s: %s" % (x.filename, x.strerror))

        # Use Path objects in this method because the path arithmetic was
        # getting too hard with strings.
        src = ch.Path(os.path.realpath(src))
        dst = ch.Path(dst)
        assert (os.path.isdir(src) and not os.path.islink(src))
        assert (os.path.isdir(dst) and not os.path.islink(dst))
        ch.DEBUG("copying named directory: %s -> %s" % (src, dst))
        for (dirpath, dirnames, filenames) in os.walk(src, onerror=onerror):
            dirpath = ch.Path(dirpath)
            subdir = dirpath.relative_to(src)
            dst_dir = dst // subdir
            # dirnames can contain symlinks, which we handle as files, so we'll
            # rebuild it; the walk will not descend into those "directories".
            dirnames2 = dirnames.copy()  # shallow copy
            dirnames[:] = list()  # clear in place
            for d in dirnames2:
                d = ch.Path(d)
                src_path = dirpath // d
                dst_path = dst_dir // d
                ch.TRACE("dir: %s -> %s" % (src_path, dst_path))
                if (os.path.islink(src_path)):
                    filenames.append(d)  # symlink, handle as file
                    ch.TRACE("symlink to dir, will handle as file")
                    continue
                else:
                    dirnames.append(d)  # directory, descend into later
                # If destination exists, but isn't a directory, remove it.
                if (os.path.exists(dst_path)):
                    if (os.path.isdir(dst_path)
                            and not os.path.islink(dst_path)):
                        ch.TRACE("dst_path exists and is a directory")
                    else:
                        ch.TRACE("dst_path exists, not a directory, removing")
                        ch.unlink(dst_path)
                # If destination directory doesn't exist, create it.
                if (not os.path.exists(dst_path)):
                    ch.TRACE("mkdir dst_path")
                    ch.ossafe(os.mkdir, "can't mkdir: %s" % dst_path, dst_path)
                # Copy metadata, now that we know the destination exists and is a
                # directory.
                ch.ossafe(shutil.copystat,
                          "can't copy metadata: %s -> %s" %
                          (src_path, dst_path),
                          src_path,
                          dst_path,
                          follow_symlinks=False)
            for f in filenames:
                f = ch.Path(f)
                src_path = dirpath // f
                dst_path = dst_dir // f
                ch.TRACE("file or symlink via copy2: %s -> %s" %
                         (src_path, dst_path))
                if (not (os.path.isfile(src_path)
                         or os.path.islink(src_path))):
                    ch.FATAL("can't COPY: unknown file type: %s" % src_path)
                if (os.path.exists(dst_path)):
                    ch.TRACE("destination exists, removing")
                    if (os.path.isdir(dst_path)
                            and not os.path.islink(dst_path)):
                        ch.rmtree(dst_path)
                    else:
                        ch.unlink(dst_path)
                ch.copy2(src_path, dst_path, follow_symlinks=False)