Ejemplo n.º 1
0
    def test_short_id(self):
        image = Image(attrs={'Id': 'sha256:b6846070672ce4e8f1f91564ea6782bd675'
                                   'f69d65a6f73ef6262057ad0a15dcd'})
        assert image.short_id == 'sha256:b684607067'

        image = Image(attrs={'Id': 'b6846070672ce4e8f1f91564ea6782bd675'
                                   'f69d65a6f73ef6262057ad0a15dcd'})
        assert image.short_id == 'b684607067'
Ejemplo n.º 2
0
    def test_tags(self):
        image = Image(attrs={'RepoTags': ['test_image:latest']})
        assert image.tags == ['test_image:latest']

        image = Image(attrs={'RepoTags': ['<none>:<none>']})
        assert image.tags == []

        image = Image(attrs={'RepoTags': None})
        assert image.tags == []
class DockerImage:
    image: Image()

    def __init__(self, package_name):
        self.package_name = package_name
        self.cmds = []
        self.special_binaries = {
            "ADD": [],
            "CMD": [],
            "COPY": [],
            "ENTRYPOINT": [],
            "ARG": [],
            "ENV": [],
            "UNKNOWN": []
        }
        self.identify_image()
        self.get_image_history()

    def identify_image(self):
        images = []
        for image in docker_client.images.list(all=True):
            for tag in image.tags:
                if self.package_name in tag:
                    images.append(image)
                    break

        if images.__len__() > 1:
            logging.error("multiple images found for tag {}, please specify a specific one".format(self.package_name))
            for image in images:
                logging.error(
                    "{} with tags \n\t- {}".format(image.id, "\n\t- ".join(docker_client.images.get(image.id).tags)))
        elif images.__len__() == 0:
            logging.warning("no image found with tag {}, pulling...".format(self.package_name))
            docker_client.images.pull(repository='dockerhub.com', tag=self.package_name)
            # todo: add fail conditions here
            pulled_images.append(self.package_name)
        else:
            # we have exactly one
            self.image = images[0]
            logging.info("{} image found: {}".format(self.package_name, self.image.id))

    def get_image_history(self):
        history_lines = docker_client.images.get(self.image.id).history()
        for index, line in enumerate(reversed(history_lines)):
            trimmed_line = line['CreatedBy'].replace("/bin/sh -c", "").replace("#(nop)", "").lstrip()

            # categorize all cmds for later checking
            categorized = False
            for key in self.special_binaries.keys():
                if trimmed_line[0:len(key)] == key:
                    self.special_binaries[key].append({'line': trimmed_line, 'line_number': index})
                    categorized = True
            if not categorized:
                self.special_binaries['UNKNOWN'].append({'line': trimmed_line, 'line_number': index})

            self.cmds.append(trimmed_line)
            logging.debug(trimmed_line)

        logging.debug(json.dumps(self.special_binaries, indent=3))

    def flatten_cmds(self, specific_cmd):
        return [add_cmd['line'] for add_cmd in self.special_binaries[specific_cmd]]

    def print_add_cmds(self):
        logging.info("{}: \n\t- {}".format(self.package_name, "\n\t- ".join(self.flatten_cmds('ADD'))))

    def is_compatible_with(self, other_docker_image):
        assert isinstance(other_docker_image, DockerImage)
        logging.info("comparing the ADD cmds for docker images: {} and {}".format(self.package_name,
                                                                                  other_docker_image.package_name))
        self.print_add_cmds()
        other_docker_image.print_add_cmds()
        return self.flatten_cmds('ADD') == other_docker_image.flatten_cmds('ADD')

    def has_no_copies(self):
        return self.flatten_cmds('COPY').__len__() == 0

    def extend_from(self, base_image):
        assert isinstance(base_image, DockerImage)
        if not self.has_no_copies():
            if self.flatten_cmds('COPY') == base_image.flatten_cmds('COPY'):
                pass
            else:
                logging.error('image {} has copy commands, aborting...'.format(self.package_name))
                return
        elif not self.is_compatible_with(base_image):
            logging.error(
                'images {} and {} are non compatible (different base ADD cmds), aborting...'.format(self.package_name,
                                                                                                    base_image.package_name))
            return

        # merging the other with myself
        final_cmds = []
        cmds_to_skip = self.flatten_cmds('ADD') + self.flatten_cmds('CMD') + self.flatten_cmds('COPY') + \
                       self.flatten_cmds('ENTRYPOINT')

        final_cmds.append('FROM {}'.format(base_image.package_name))
        for cmd in self.cmds:
            if cmd in cmds_to_skip:
                continue
            elif cmd in self.flatten_cmds('UNKNOWN'):
                cmd = 'RUN {}'.format(cmd)
            final_cmds.append(cmd)

        return final_cmds