Beispiel #1
0
    def prepare(self):
        """Prepare self.image for pushing to self.dst_ref. Return tuple: (list
         of gzipped layer tarball paths, config as a sequence of bytes,
         manifest as a sequence of bytes).

         There is not currently any support for re-using any previously
         prepared files already in the upload cache, because we don't yet have
         a way to know if these have changed until they are already build."""
        ch.mkdirs(ch.storage.upload_cache)
        tars_uc = self.image.tarballs_write(ch.storage.upload_cache)
        tars_c = list()
        config = self.config_new()
        manifest = self.manifest_new()
        # Prepare layers.
        for (i, tar_uc) in enumerate(tars_uc, start=1):
            ch.INFO("layer %d/%d: preparing" % (i, len(tars_uc)))
            path_uc = ch.storage.upload_cache // tar_uc
            hash_uc = ch.file_hash(path_uc)
            config["rootfs"]["diff_ids"].append("sha256:" + hash_uc)
            #size_uc = ch.file_size(path_uc)
            path_c = ch.file_gzip(path_uc, ["-9", "--no-name"])
            tar_c = path_c.name
            hash_c = ch.file_hash(path_c)
            size_c = ch.file_size(path_c)
            tars_c.append((hash_c, path_c))
            manifest["layers"].append({
                "mediaType": ch.TYPE_LAYER,
                "size": size_c,
                "digest": "sha256:" + hash_c
            })
        # Prepare metadata.
        ch.INFO("preparing metadata")
        config_bytes = json.dumps(config, indent=2).encode("UTF-8")
        config_hash = ch.bytes_hash(config_bytes)
        manifest["config"]["size"] = len(config_bytes)
        manifest["config"]["digest"] = "sha256:" + config_hash
        ch.DEBUG("config: %s\n%s" %
                 (config_hash, config_bytes.decode("UTF-8")))
        manifest_bytes = json.dumps(manifest, indent=2).encode("UTF-8")
        ch.DEBUG("manifest:\n%s" % manifest_bytes.decode("UTF-8"))
        # Store for the next steps.
        self.layers = tars_c
        self.config = config_bytes
        self.manifest = manifest_bytes
Beispiel #2
0
 def download(self):
     "Download image metadata and layers and put them in the download cache."
     # Spec: https://docs.docker.com/registry/spec/manifest-v2-2/
     ch.VERBOSE("downloading image: %s" % self.image)
     ch.mkdirs(ch.storage.download_cache)
     # fat manifest
     if (ch.arch != "yolo"):
         self.fatman_load()
         if (self.architectures is not None):
             if (ch.arch not in self.architectures):
                 ch.FATAL(
                     "requested arch unavailable: %s not one of: %s" %
                     (ch.arch, " ".join(sorted(self.architectures.keys()))))
         elif (ch.arch == "amd64"):
             # We're guessing that enough arch-unaware images are amd64 to
             # barge ahead if requested architecture is amd64.
             ch.arch = "yolo"
             ch.WARNING("image is architecture-unaware")
             ch.WARNING("requested arch is amd64; switching to --arch=yolo")
         else:
             ch.FATAL("image is architecture-unaware; try --arch=yolo (?)")
     # manifest
     self.manifest_load()
     # config
     ch.VERBOSE("config path: %s" % self.config_path)
     if (self.config_path is not None):
         if (os.path.exists(self.config_path) and self.use_cache):
             ch.INFO("config: using existing file")
         else:
             ch.INFO("config: downloading")
             self.registry.blob_to_file(self.config_hash, self.config_path)
     # layers
     for (i, lh) in enumerate(self.layer_hashes, start=1):
         path = self.layer_path(lh)
         ch.VERBOSE("layer path: %s" % path)
         ch.INFO("layer %d/%d: %s: " % (i, len(self.layer_hashes), lh[:7]),
                 end="")
         if (os.path.exists(path) and self.use_cache):
             ch.INFO("using existing file")
         else:
             ch.INFO("downloading")
             self.registry.blob_to_file(lh, path)
Beispiel #3
0
 def download(self, use_cache):
    """Download image metadata and layers and put them in the download
       cache. If use_cache is True (the default), anything already in the
       cache is skipped, otherwise download it anyway, overwriting what's in
       the cache."""
    # Spec: https://docs.docker.com/registry/spec/manifest-v2-2/
    dl = ch.Registry_HTTP(self.image.ref)
    ch.VERBOSE("downloading image: %s" % dl.ref)
    ch.mkdirs(ch.storage.download_cache)
    # manifest
    if (os.path.exists(self.manifest_path) and use_cache):
       ch.INFO("manifest: using existing file")
    else:
       ch.INFO("manifest: downloading")
       dl.manifest_to_file(self.manifest_path)
    self.manifest_load()
    # config
    ch.VERBOSE("config path: %s" % self.config_path)
    if (self.config_path is not None):
       if (os.path.exists(self.config_path) and use_cache):
          ch.INFO("config: using existing file")
       else:
          ch.INFO("config: downloading")
          dl.blob_to_file(self.config_hash, self.config_path)
    # layers
    for (i, lh) in enumerate(self.layer_hashes, start=1):
       path = self.layer_path(lh)
       ch.VERBOSE("layer path: %s" % path)
       ch.INFO("layer %d/%d: %s: "% (i, len(self.layer_hashes), lh[:7]),
               end="")
       if (os.path.exists(path) and use_cache):
          ch.INFO("using existing file")
       else:
          ch.INFO("downloading")
          dl.blob_to_file(lh, path)
    dl.close()
Beispiel #4
0
 def execute_(self):
    env.chdir(self.path)
    ch.mkdirs(images[image_i].unpack_path + env.workdir)
Beispiel #5
0
 def execute_(self):
    # 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 source 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
    ch.DEBUG("context: " + context)
    # Do the copy.
    srcs = list()
    for src in self.srcs:
       if (os.path.normpath(src).startswith("..")):
          ch.FATAL("can't COPY: %s climbs outside context" % src)
       for i in glob.glob(context + "/" + src):
          srcs.append(i)
    if (len(srcs) == 0):
       ch.FATAL("can't COPY: no sources exist")
    dst = images[image_i].unpack_path + "/"
    if (not self.dst.startswith("/")):
       dst += env.workdir + "/"
    dst += self.dst
    if (dst.endswith("/") or len(srcs) > 1 or os.path.isdir(srcs[0])):
       # Create destination directory.
       if (dst.endswith("/")):
          dst = dst[:-1]
       if (os.path.exists(dst) and not os.path.isdir(dst)):
          ch.FATAL("can't COPY: %s exists but is not a directory" % dst)
       ch.mkdirs(dst)
    for src in srcs:
       # Check for symlinks to outside context.
       src_real = os.path.realpath(src)
       context_real = os.path.realpath(context)
       if (not os.path.commonpath([src_real, context_real]) \
               .startswith(context_real)):
          ch.FATAL("can't COPY: %s climbs outside context via symlink" % src)
       # Do the copy.
       if (os.path.isfile(src)):   # or symlink to file
          ch.DEBUG("COPY via copy2 file %s to %s" % (src, dst))
          ch.copy2(src, dst, follow_symlinks=True)
       elif (os.path.isdir(src)):  # or symlink to directory
          # Copy *contents* of src, not src itself. Note: shutil.copytree()
          # has a parameter dirs_exist_ok that I think will make this easier
          # in Python 3.8.
          ch.DEBUG("COPY dir %s to %s" % (src, dst))
          if (not os.path.isdir(dst)):
             ch.FATAL("can't COPY: destination not a directory: %s to %s"
                      % (src, dst))
          for src2_basename in ch.ossafe(
                os.listdir, "can't list directory: %s" % src, src):
             src2 = src + "/" + src2_basename
             if (os.path.islink(src2)):
                # Symlinks within directories do not get dereferenced.
                ch.DEBUG("symlink via copy2: %s to %s" % (src2, dst))
                ch.copy2(src2, dst, follow_symlinks=False)
             elif (os.path.isfile(src2)):  # not symlink to file
                ch.DEBUG("file via copy2: %s to %s" % (src2, dst))
                ch.copy2(src2, dst)
             elif (os.path.isdir(src2)):   # not symlink to directory
                dst2 = dst + "/" + src2_basename
                ch.DEBUG("directory via copytree: %s to %s" % (src2, dst2))
                ch.copytree(src2, dst2, symlinks=True,
                            ignore_dangling_symlinks=True)
             else:
                ch.FATAL("can't COPY unknown file type: %s" % src2)
       else:
          ch.FATAL("can't COPY unknown file type: %s" % src)
Beispiel #6
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)