def change_from_in_df(self, build_dir: BuildDir) -> None:
        self.log.info(
            "Updating FROM instructions in %s Dockerfile to pin parent images",
            build_dir.platform)
        dfp = build_dir.dockerfile

        df_images = self.workflow.data.dockerfile_images
        build_base = df_images.base_image

        if not df_images.base_from_scratch:
            # do some sanity checks to defend against bugs and rogue plugins
            self._sanity_check(dfp.baseimage, build_base)

        # check for lost parent images
        missing_set = set()
        for df_img in dfp.parent_images:
            if base_image_is_scratch(df_img):
                continue
            try:
                df_images[df_img]
            except KeyError:
                missing_set.add(df_img)
        if missing_set:
            # this would indicate another plugin modified parent_images out of sync
            # with the Dockerfile or some other code bug
            raise ParentImageMissing(
                f"Lost parent image(s) from Dockerfile images and their "
                f"parents:"
                f" {missing_set}")

        # replace image tags with manifest digests generated by check_base_image
        new_parents = []

        for df_img in dfp.parent_images:
            if base_image_is_scratch(df_img):
                new_parents.append(df_img)
            else:
                new_parents.append(df_images[df_img].to_str())

        # update parent_images in Dockerfile
        dfp.parent_images = new_parents

        if df_images.base_from_scratch:
            self.log.debug("base image '%s' left unchanged", dfp.baseimage)
        else:
            self.log.debug(
                "for base image '%s' using image with pullspec '%s'",
                dfp.baseimage, build_base)
 def set_base_image(self, base_image, parents_pulled=True, insecure=False, dockercfg_path=None):
     self.base_from_scratch = base_image_is_scratch(base_image)
     if not self.custom_base_image:
         self.custom_base_image = base_image_is_custom(base_image)
     self.base_image = ImageName.parse(base_image)
     self.original_base_image = self.original_base_image or self.base_image
     self.recreate_parent_images()
     if not self.base_from_scratch:
         self.parent_images[self.original_base_image] = self.base_image
Ejemplo n.º 3
0
 def layer_index(self):
     if self._layer_index is None:
         # Default layer index is 1, because base and 'FROM scratch' images
         #     *always* have 2 layers
         self._layer_index = 1
     if not base_image_is_scratch(self.dfp.baseimage):
         inspect = self.workflow.builder.base_image_inspect
         self._layer_index = len(inspect[INSPECT_ROOTFS][INSPECT_ROOTFS_LAYERS])
     return self._layer_index
    def set_base_image(self, base_image, parents_pulled=True, insecure=False):
        self.base_from_scratch = base_image_is_scratch(base_image)
        if not self.custom_base_image:
            self.custom_base_image = base_image_is_custom(base_image)
        self.base_image = ImageName.parse(base_image)
        self.original_base_image = self.original_base_image or self.base_image
        self.recreate_parent_images()

        if not self.base_from_scratch:
            self.parent_images[self.original_base_image] = self.base_image
Ejemplo n.º 5
0
    def set_df_path(self, path):
        self._df_path = path
        dfp = df_parser(path)
        base = dfp.baseimage
        if base is None:
            raise RuntimeError("no base image specified in Dockerfile")
        self.set_base_image(base)
        logger.debug("base image specified in dockerfile = '%s'",
                     self.base_image)
        self.parent_images.clear()
        custom_base_images = set()
        for image in dfp.parent_images:
            image_name = ImageName.parse(image)
            if base_image_is_scratch(image_name.get_repo()):
                image_name.tag = None
                self.parents_ordered.append(image_name.to_str())
                continue
            image_str = image_name.to_str()
            if base_image_is_custom(image_str):
                custom_base_images.add(image_str)
                self.custom_parent_image = True
            self.parents_ordered.append(image_str)
            self.parent_images[image_name] = None

        if len(custom_base_images) > 1:
            raise NotImplementedError("multiple different custom base images"
                                      " aren't allowed in Dockerfile")

        # validate user has not specified COPY --from=image
        builders = []
        for stmt in dfp.structure:
            if stmt['instruction'] == 'FROM':
                # extract "bar" from "foo as bar" and record as build stage
                match = re.search(r'\S+ \s+  as  \s+ (\S+)', stmt['value'],
                                  re.I | re.X)
                builders.append(match.group(1) if match else None)
            elif stmt['instruction'] == 'COPY':
                match = re.search(r'--from=(\S+)', stmt['value'], re.I)
                if not match:
                    continue
                stage = match.group(1)
                # error unless the --from is the index or name of a stage we've seen
                if any(stage in [str(idx), builder]
                       for idx, builder in enumerate(builders)):
                    continue
                raise RuntimeError(
                    dedent("""\
                    OSBS does not support COPY --from unless it matches a build stage.
                    Dockerfile instruction was:
                      {}
                    To use an image with COPY --from, specify it in a stage with FROM, e.g.
                      FROM {} AS source
                      FROM ...
                      COPY --from=source <src> <dest>
                    """).format(stmt['content'], stage))
Ejemplo n.º 6
0
    def set_base_image(self, base_image, parents_pulled=True, insecure=False):
        self.base_from_scratch = base_image_is_scratch(base_image)
        self.base_image = ImageName.parse(base_image)
        self.original_base_image = self.original_base_image or self.base_image
        self.recreate_parent_images()

        if not self.base_from_scratch:
            self.parent_images[self.original_base_image] = self.base_image
        self.parents_pulled = parents_pulled
        self.base_image_insecure = insecure
        logger.info("set base image to '%s' with original base '%s'",
                    self.base_image, self.original_base_image)
Ejemplo n.º 7
0
    def set_df_path(self, path):
        self._df_path = path
        dfp = df_parser(path)
        base = dfp.baseimage
        if base is None:
            raise RuntimeError("no base image specified in Dockerfile")
        self.set_base_image(base)
        logger.debug("base image specified in dockerfile = '%s'", self.base_image)
        self.parent_images.clear()
        custom_base_images = set()
        for image in dfp.parent_images:
            image_name = ImageName.parse(image)
            if base_image_is_scratch(image_name.get_repo()):
                image_name.tag = None
                self.parents_ordered.append(image_name.to_str())
                continue
            image_str = image_name.to_str()
            if base_image_is_custom(image_str):
                custom_base_images.add(image_str)
                self.custom_parent_image = True
            self.parents_ordered.append(image_str)
            self.parent_images[image_name] = None

        if len(custom_base_images) > 1:
            raise NotImplementedError("multiple different custom base images"
                                      " aren't allowed in Dockerfile")

        # validate user has not specified COPY --from=image
        builders = []
        for stmt in dfp.structure:
            if stmt['instruction'] == 'FROM':
                # extract "bar" from "foo as bar" and record as build stage
                match = re.search(r'\S+ \s+  as  \s+ (\S+)', stmt['value'], re.I | re.X)
                builders.append(match.group(1) if match else None)
            elif stmt['instruction'] == 'COPY':
                match = re.search(r'--from=(\S+)', stmt['value'], re.I)
                if not match:
                    continue
                stage = match.group(1)
                # error unless the --from is the index or name of a stage we've seen
                if any(stage in [str(idx), builder] for idx, builder in enumerate(builders)):
                    continue
                raise RuntimeError(dedent("""\
                    OSBS does not support COPY --from unless it matches a build stage.
                    Dockerfile instruction was:
                      {}
                    To use an image with COPY --from, specify it in a stage with FROM, e.g.
                      FROM {} AS source
                      FROM ...
                      COPY --from=source <src> <dest>
                    """).format(stmt['content'], stage))
Ejemplo n.º 8
0
    def set_base_image(self, base_image, parents_pulled=True, insecure=False, dockercfg_path=None):
        self.base_from_scratch = base_image_is_scratch(base_image)
        if not self.custom_base_image:
            self.custom_base_image = base_image_is_custom(base_image)
        self.base_image = ImageName.parse(base_image)
        self.original_base_image = self.original_base_image or self.base_image
        self.recreate_parent_images()

        if not self.base_from_scratch:
            self.parent_images[self.original_base_image] = self.base_image
        self.parents_pulled = parents_pulled
        self.base_image_insecure = insecure
        self.base_image_dockercfg_path = dockercfg_path
        logger.info("set base image to '%s' with original base '%s'", self.base_image,
                    self.original_base_image)
Ejemplo n.º 9
0
    def run(self):
        """
        Check parent images to ensure they only come from allowed registries.
        """
        self.manifest_list_cache.clear()

        digest_fetching_exceptions = []
        for parent in self.workflow.data.dockerfile_images.keys():
            if base_image_is_custom(parent.to_str()) or base_image_is_scratch(
                    parent.to_str()):
                continue

            image = parent
            # base_image_key is an ImageName, so compare parent as an ImageName also
            if image == self.workflow.data.dockerfile_images.base_image_key:
                image = self._resolve_base_image()

            self._ensure_image_registry(image)
            self._validate_platforms_in_image(image)

            try:
                digest = self._fetch_manifest_digest(image)
            except RuntimeError as exc:
                digest_fetching_exceptions.append(exc)
                continue

            image_with_digest = self._pin_to_digest(image, digest)
            self.log.info("Replacing image '%s' with '%s'", image,
                          image_with_digest)

            self.workflow.data.dockerfile_images[parent] = image_with_digest
            self.workflow.data.parent_images_digests[str(
                image_with_digest)] = digest

        if digest_fetching_exceptions:
            raise RuntimeError(
                'Error when extracting parent images manifest digests: {}'.
                format(digest_fetching_exceptions))
    def run(self):
        builder = self.workflow.builder
        dfp = df_parser(builder.df_path)

        organization = get_registries_organization(self.workflow)
        df_base = ImageName.parse(dfp.baseimage)
        if organization and not base_image_is_custom(dfp.baseimage):
            df_base.enclose(organization)
        build_base = builder.base_image

        if not self.workflow.builder.base_from_scratch:
            # do some sanity checks to defend against bugs and rogue plugins
            self._sanity_check(df_base, build_base, builder)

        self.log.info("parent_images '%s'", builder.parent_images)
        unresolved = [
            key for key, val in builder.parent_images.items() if not val
        ]
        if unresolved:
            # this would generally mean pull_base_image didn't run and/or
            # custom plugins modified parent_images; treat it as an error.
            raise ParentImageUnresolved(
                "Parent image(s) unresolved: {}".format(unresolved))

        # enclose images from dfp
        enclosed_parent_images = []
        for df_img in dfp.parent_images:
            if base_image_is_scratch(df_img):
                enclosed_parent_images.append(df_img)
                continue
            parent = ImageName.parse(df_img)
            if organization and not base_image_is_custom(df_img):
                parent.enclose(organization)
            enclosed_parent_images.append(parent)

        missing = [
            df_img for df_img in enclosed_parent_images
            if df_img not in builder.parent_images
        ]
        missing_set = set(missing)
        if SCRATCH_FROM in missing_set:
            missing_set.remove(SCRATCH_FROM)
        if missing_set:
            # this would indicate another plugin modified parent_images out of sync
            # with the Dockerfile or some other code bug
            raise ParentImageMissing(
                "Lost parent image(s) from Dockerfile: {}".format(missing_set))

        # docker inspect all parent images so we can address them by Id
        parent_image_ids = {}
        for img, new_img in builder.parent_images.items():
            inspection = builder.parent_image_inspect(new_img)
            try:
                parent_image_ids[img] = inspection['Id']
            except KeyError:  # unexpected code bugs or maybe docker weirdness
                self.log.error(
                    "Id for image %s is missing in inspection: '%s'", new_img,
                    inspection)
                raise NoIdInspection("Could not inspect Id for image " +
                                     str(new_img))

        # update the parents in Dockerfile
        new_parents = []
        for parent in enclosed_parent_images:
            if base_image_is_scratch(parent):
                new_parents.append(parent)
                continue
            pid = parent_image_ids[parent]
            self.log.info("changed FROM: '%s' -> '%s'", parent, pid)
            new_parents.append(pid)
        dfp.parent_images = new_parents

        # update builder's representation of what will be built
        builder.parent_images = parent_image_ids

        if self.workflow.builder.base_from_scratch:
            return

        builder.set_base_image(parent_image_ids[df_base])
        self.log.debug("for base image '%s' using local image '%s', id '%s'",
                       df_base, build_base, parent_image_ids[df_base])
Ejemplo n.º 11
0
def image_is_inspectable(image: Union[str, ImageName]) -> bool:
    """Check if we should expect the image to be inspectable."""
    im = str(image)
    return not (util.base_image_is_scratch(im)
                or util.base_image_is_custom(im))
Ejemplo n.º 12
0
    def run(self):
        builder = self.workflow.builder
        dfp = df_parser(builder.df_path)
        builder.original_df = dfp.content

        df_base = dfp.baseimage
        build_base = builder.dockerfile_images.base_image

        if not self.workflow.builder.dockerfile_images.base_from_scratch:
            # do some sanity checks to defend against bugs and rogue plugins
            self._sanity_check(dfp.baseimage, build_base, builder)

        self.log.info("parent_images '%s'", builder.dockerfile_images.keys())
        unresolved = [
            key for key, val in builder.dockerfile_images.items() if not val
        ]
        if unresolved:
            # this would generally mean pull_base_image didn't run and/or
            # custom plugins modified parent_images; treat it as an error.
            raise ParentImageUnresolved(
                "Parent image(s) unresolved: {}".format(unresolved))

        # check for lost parent images
        missing_set = set()
        for df_img in dfp.parent_images:
            if base_image_is_scratch(df_img):
                continue
            try:
                builder.dockerfile_images[df_img]
            except KeyError:
                missing_set.add(df_img)
        if missing_set:
            # this would indicate another plugin modified parent_images out of sync
            # with the Dockerfile or some other code bug
            raise ParentImageMissing(
                "Lost parent image(s) from Dockerfile: {}".format(missing_set))

        # docker inspect all parent images so we can address them by Id
        parent_image_ids = {}
        new_parents = []

        for df_img in dfp.parent_images:
            if base_image_is_scratch(df_img):
                new_parents.append(df_img)
                continue
            local_image = builder.dockerfile_images[df_img]
            inspection = builder.parent_image_inspect(local_image)

            try:
                parent_image_ids[df_img] = inspection['Id']
                new_parents.append(inspection['Id'])
            except KeyError as exc:  # unexpected code bugs or maybe docker weirdness
                self.log.error(
                    "Id for image %s is missing in inspection: '%s'", df_img,
                    inspection)
                raise NoIdInspection("Could not inspect Id for image " +
                                     df_img) from exc

        # update builder's representation of what will be built
        for df_img in dfp.parent_images:
            if base_image_is_scratch(df_img):
                continue
            builder.dockerfile_images[df_img] = parent_image_ids[df_img]

        # update parent_images in Dockerfile
        dfp.parent_images = new_parents

        if builder.dockerfile_images.base_from_scratch:
            return

        self.log.debug("for base image '%s' using local image '%s', id '%s'",
                       df_base, build_base, parent_image_ids[df_base])
    def run(self):
        builder = self.workflow.builder
        dfp = df_parser(builder.df_path)

        organization = get_registries_organization(self.workflow)
        df_base = ImageName.parse(dfp.baseimage)
        if organization and not base_image_is_custom(dfp.baseimage):
            df_base.enclose(organization)
        build_base = builder.base_image

        if not self.workflow.builder.base_from_scratch:
            # do some sanity checks to defend against bugs and rogue plugins
            self._sanity_check(df_base, build_base, builder)

        self.log.info("parent_images '%s'", builder.parent_images)
        unresolved = [key for key, val in builder.parent_images.items() if not val]
        if unresolved:
            # this would generally mean pull_base_image didn't run and/or
            # custom plugins modified parent_images; treat it as an error.
            raise ParentImageUnresolved("Parent image(s) unresolved: {}".format(unresolved))

        # enclose images from dfp
        enclosed_parent_images = []
        for df_img in dfp.parent_images:
            if base_image_is_scratch(df_img):
                enclosed_parent_images.append(df_img)
                continue
            parent = ImageName.parse(df_img)
            if organization and not base_image_is_custom(df_img):
                parent.enclose(organization)
            enclosed_parent_images.append(parent)

        missing = [df_img for df_img in enclosed_parent_images
                   if df_img not in builder.parent_images]
        missing_set = set(missing)
        if SCRATCH_FROM in missing_set:
            missing_set.remove(SCRATCH_FROM)
        if missing_set:
            # this would indicate another plugin modified parent_images out of sync
            # with the Dockerfile or some other code bug
            raise ParentImageMissing("Lost parent image(s) from Dockerfile: {}".format(missing_set))

        # docker inspect all parent images so we can address them by Id
        parent_image_ids = {}
        for img, new_img in builder.parent_images.items():
            inspection = builder.parent_image_inspect(new_img)
            try:
                parent_image_ids[img] = inspection['Id']
            except KeyError:  # unexpected code bugs or maybe docker weirdness
                self.log.error(
                    "Id for image %s is missing in inspection: '%s'",
                    new_img, inspection)
                raise NoIdInspection("Could not inspect Id for image " + str(new_img))

        # update the parents in Dockerfile
        new_parents = []
        for parent in enclosed_parent_images:
            if base_image_is_scratch(parent):
                new_parents.append(parent)
                continue
            pid = parent_image_ids[parent]
            self.log.info("changed FROM: '%s' -> '%s'", parent, pid)
            new_parents.append(pid)
        dfp.parent_images = new_parents

        # update builder's representation of what will be built
        builder.parent_images = parent_image_ids

        if self.workflow.builder.base_from_scratch:
            return

        builder.set_base_image(parent_image_ids[df_base])
        self.log.debug(
            "for base image '%s' using local image '%s', id '%s'",
            df_base, build_base, parent_image_ids[df_base]
        )