Пример #1
0
    def __init__(self, client, results):

        super(ImageManager, self).__init__()

        self.client = client
        self.results = results
        parameters = self.client.module.params
        self.check_mode = self.client.check_mode

        self.source = parameters['source']
        build = parameters['build'] or dict()
        pull = parameters['pull'] or dict()
        self.archive_path = parameters['archive_path']
        self.cache_from = build.get('cache_from')
        self.container_limits = build.get('container_limits')
        self.dockerfile = build.get('dockerfile')
        self.force_source = parameters['force_source']
        self.force_absent = parameters['force_absent']
        self.force_tag = parameters['force_tag']
        self.load_path = parameters['load_path']
        self.name = parameters['name']
        self.network = build.get('network')
        self.extra_hosts = clean_dict_booleans_for_docker_api(
            build.get('etc_hosts'))
        self.nocache = build.get('nocache', False)
        self.build_path = build.get('path')
        self.pull = build.get('pull')
        self.target = build.get('target')
        self.repository = parameters['repository']
        self.rm = build.get('rm', True)
        self.state = parameters['state']
        self.tag = parameters['tag']
        self.http_timeout = build.get('http_timeout')
        self.pull_platform = pull.get('platform')
        self.push = parameters['push']
        self.buildargs = build.get('args')
        self.build_platform = build.get('platform')
        self.use_config_proxy = build.get('use_config_proxy')

        # If name contains a tag, it takes precedence over tag parameter.
        if not is_image_name_id(self.name):
            repo, repo_tag = parse_repository_tag(self.name)
            if repo_tag:
                self.name = repo
                self.tag = repo_tag

        # Sanity check: fail early when we know that something will fail later
        if self.repository and is_image_name_id(self.repository):
            self.fail("`repository` must not be an image ID; got: %s" %
                      self.repository)
        if not self.repository and self.push and is_image_name_id(self.name):
            self.fail(
                "Cannot push an image by ID; specify `repository` to tag and push the image with ID %s instead"
                % self.name)

        if self.state == 'present':
            self.present()
        elif self.state == 'absent':
            self.absent()
    def get_facts(self):
        '''
        Lookup and inspect each image name found in the names parameter.

        :returns array of image dictionaries
        '''

        results = []

        names = self.name
        if not isinstance(names, list):
            names = [names]

        for name in names:
            if is_image_name_id(name):
                self.log('Fetching image %s (ID)' % (name))
                image = self.client.find_image_by_id(name,
                                                     accept_missing_image=True)
            else:
                repository, tag = utils.parse_repository_tag(name)
                if not tag:
                    tag = 'latest'
                self.log('Fetching image %s:%s' % (repository, tag))
                image = self.client.find_image(name=repository, tag=tag)
            if image:
                results.append(image)
        return results
Пример #3
0
    def absent(self):
        '''
        Handles state = 'absent', which removes an image.

        :return None
        '''
        name = self.name
        if is_image_name_id(name):
            image = self.client.find_image_by_id(name,
                                                 accept_missing_image=True)
        else:
            image = self.client.find_image(name, self.tag)
            if self.tag:
                name = "%s:%s" % (self.name, self.tag)
        if image:
            if not self.check_mode:
                try:
                    self.client.remove_image(name, force=self.force_absent)
                except NotFound:
                    # If the image vanished while we were trying to remove it, don't fail
                    pass
                except Exception as exc:
                    self.fail("Error removing image %s - %s" %
                              (name, to_native(exc)))

            self.results['changed'] = True
            self.results['actions'].append("Removed image %s" % (name))
            self.results['image']['state'] = 'Deleted'
Пример #4
0
    def push_image(self, name, tag=None):
        '''
        If the name of the image contains a repository path, then push the image.

        :param name Name of the image to push.
        :param tag Use a specific tag.
        :return: None
        '''

        if is_image_name_id(name):
            self.fail("Cannot push an image ID: %s" % name)

        repository = name
        if not tag:
            repository, tag = parse_repository_tag(name)
        registry, repo_name = resolve_repository_name(repository)

        self.log("push %s to %s/%s:%s" % (self.name, registry, repo_name, tag))

        if registry:
            self.results['actions'].append(
                "Pushed image %s to %s/%s:%s" %
                (self.name, registry, repo_name, tag))
            self.results['changed'] = True
            if not self.check_mode:
                status = None
                try:
                    changed = False
                    for line in self.client.push(repository,
                                                 tag=tag,
                                                 stream=True,
                                                 decode=True):
                        self.log(line, pretty_print=True)
                        if line.get('errorDetail'):
                            raise Exception(line['errorDetail']['message'])
                        status = line.get('status')
                        if status == 'Pushing':
                            changed = True
                    self.results['changed'] = changed
                except Exception as exc:
                    if 'unauthorized' in str(exc):
                        if 'authentication required' in str(exc):
                            self.fail(
                                "Error pushing image %s/%s:%s - %s. Try logging into %s first."
                                % (registry, repo_name, tag, to_native(exc),
                                   registry))
                        else:
                            self.fail(
                                "Error pushing image %s/%s:%s - %s. Does the repository exist?"
                                % (registry, repo_name, tag, str(exc)))
                    self.fail("Error pushing image %s: %s" %
                              (repository, to_native(exc)))
                self.results['image'] = self.client.find_image(name=repository,
                                                               tag=tag)
                if not self.results['image']:
                    self.results['image'] = dict()
                self.results['image']['push_status'] = status
Пример #5
0
    def load_images(self):
        '''
        Load images from a .tar archive
        '''
        # Load image(s) from file
        load_output = []
        try:
            self.log("Opening image {0}".format(self.path))
            with open(self.path, 'rb') as image_tar:
                self.log("Loading images from {0}".format(self.path))
                for line in self.client.load_image(image_tar):
                    self.log(line, pretty_print=True)
                    self._extract_output_line(line, load_output)
        except EnvironmentError as exc:
            if exc.errno == errno.ENOENT:
                self.client.fail("Error opening archive {0} - {1}".format(
                    self.path, to_native(exc)))
            self.client.fail("Error loading archive {0} - {1}".format(
                self.path, to_native(exc)),
                             stdout='\n'.join(load_output))
        except Exception as exc:
            self.client.fail("Error loading archive {0} - {1}".format(
                self.path, to_native(exc)),
                             stdout='\n'.join(load_output))

        # Collect loaded images
        loaded_images = []
        for line in load_output:
            if line.startswith('Loaded image:'):
                loaded_images.append(line[len('Loaded image:'):].strip())
            if line.startswith('Loaded image ID:'):
                loaded_images.append(line[len('Loaded image ID:'):].strip())

        if not loaded_images:
            self.client.fail(
                "Detected no loaded images. Archive potentially corrupt?",
                stdout='\n'.join(load_output))

        images = []
        for image_name in loaded_images:
            if is_image_name_id(image_name):
                images.append(self.client.find_image_by_id(image_name))
            elif ':' in image_name:
                image_name, tag = image_name.rsplit(':', 1)
                images.append(self.client.find_image(image_name, tag))
            else:
                self.client.module.warn(
                    'Image name "{0}" is neither ID nor has a tag'.format(
                        image_name))

        self.results['image_names'] = loaded_images
        self.results['images'] = images
        self.results['changed'] = True
        self.results['stdout'] = '\n'.join(load_output)
Пример #6
0
    def __init__(self, client, results):

        super(ImageManager, self).__init__()

        self.client = client
        self.results = results
        parameters = self.client.module.params
        self.check_mode = self.client.check_mode

        self.source = parameters['source']
        build = parameters['build'] or dict()
        pull = parameters['pull'] or dict()
        self.archive_path = parameters['archive_path']
        self.cache_from = build.get('cache_from')
        self.container_limits = build.get('container_limits')
        self.dockerfile = build.get('dockerfile')
        self.force_source = parameters['force_source']
        self.force_absent = parameters['force_absent']
        self.force_tag = parameters['force_tag']
        self.load_path = parameters['load_path']
        self.name = parameters['name']
        self.network = build.get('network')
        self.extra_hosts = clean_dict_booleans_for_docker_api(
            build.get('etc_hosts'))
        self.nocache = build.get('nocache', False)
        self.build_path = build.get('path')
        self.pull = build.get('pull')
        self.target = build.get('target')
        self.repository = parameters['repository']
        self.rm = build.get('rm', True)
        self.state = parameters['state']
        self.tag = parameters['tag']
        self.http_timeout = build.get('http_timeout')
        self.pull_platform = pull.get('platform')
        self.push = parameters['push']
        self.buildargs = build.get('args')
        self.build_platform = build.get('platform')
        self.use_config_proxy = build.get('use_config_proxy')

        # If name contains a tag, it takes precedence over tag parameter.
        if not is_image_name_id(self.name):
            repo, repo_tag = parse_repository_tag(self.name)
            if repo_tag:
                self.name = repo
                self.tag = repo_tag

        if self.state == 'present':
            self.present()
        elif self.state == 'absent':
            self.absent()
Пример #7
0
    def archive_image(self, name, tag):
        '''
        Archive an image to a .tar file. Called when archive_path is passed.

        :param name - name of the image. Type: str
        :return None
        '''

        if not tag:
            tag = "latest"

        if is_image_name_id(name):
            image = self.client.find_image_by_id(name,
                                                 accept_missing_image=True)
            image_name = name
        else:
            image = self.client.find_image(name=name, tag=tag)
            image_name = "%s:%s" % (name, tag)

        if not image:
            self.log("archive image: image %s not found" % image_name)
            return

        self.results['actions'].append('Archived image %s to %s' %
                                       (image_name, self.archive_path))
        self.results['changed'] = True
        if not self.check_mode:
            self.log("Getting archive of image %s" % image_name)
            try:
                saved_image = self.client.get_image(image_name)
            except Exception as exc:
                self.fail("Error getting image %s - %s" %
                          (image_name, to_native(exc)))

            try:
                with open(self.archive_path, 'wb') as fd:
                    if self.client.docker_py_version >= LooseVersion('3.0.0'):
                        for chunk in saved_image:
                            fd.write(chunk)
                    else:
                        for chunk in saved_image.stream(2048,
                                                        decode_content=False):
                            fd.write(chunk)
            except Exception as exc:
                self.fail("Error writing image archive %s - %s" %
                          (self.archive_path, to_native(exc)))

        if image:
            self.results['image'] = image
Пример #8
0
    def tag_image(self, name, tag, repository, push=False):
        '''
        Tag an image into a repository.

        :param name: name of the image. required.
        :param tag: image tag.
        :param repository: path to the repository. required.
        :param push: bool. push the image once it's tagged.
        :return: None
        '''
        repo, repo_tag = parse_repository_tag(repository)
        if not repo_tag:
            repo_tag = "latest"
            if tag:
                repo_tag = tag
        image = self.client.find_image(name=repo, tag=repo_tag)
        found = 'found' if image else 'not found'
        self.log("image %s was %s" % (repo, found))

        if not image or self.force_tag:
            image_name = name
            if not is_image_name_id(name) and tag and not name.endswith(':' +
                                                                        tag):
                image_name = "%s:%s" % (name, tag)
            self.log("tagging %s to %s:%s" % (image_name, repo, repo_tag))
            self.results['changed'] = True
            self.results['actions'].append("Tagged image %s to %s:%s" %
                                           (image_name, repo, repo_tag))
            if not self.check_mode:
                try:
                    # Finding the image does not always work, especially running a localhost registry. In those
                    # cases, if we don't set force=True, it errors.
                    tag_status = self.client.tag(image_name,
                                                 repo,
                                                 tag=repo_tag,
                                                 force=True)
                    if not tag_status:
                        raise Exception("Tag operation failed.")
                except Exception as exc:
                    self.fail("Error: failed to tag image - %s" %
                              to_native(exc))
                self.results['image'] = self.client.find_image(name=repo,
                                                               tag=repo_tag)
                if image and image['Id'] == self.results['image']['Id']:
                    self.results['changed'] = False

        if push:
            self.push_image(repo, repo_tag)
Пример #9
0
    def absent(self):
        '''
        Handles state = 'absent', which removes an image.

        :return None
        '''
        name = self.name
        if is_image_name_id(name):
            image = self.client.find_image_by_id(name)
        else:
            image = self.client.find_image(name, self.tag)
            if self.tag:
                name = "%s:%s" % (self.name, self.tag)
        if image:
            if not self.check_mode:
                try:
                    self.client.remove_image(name, force=self.force_absent)
                except Exception as exc:
                    self.fail("Error removing image %s - %s" % (name, str(exc)))

            self.results['changed'] = True
            self.results['actions'].append("Removed image %s" % (name))
            self.results['image']['state'] = 'Deleted'
Пример #10
0
    def load_image(self):
        '''
        Load an image from a .tar archive

        :return: image dict
        '''
        # Load image(s) from file
        load_output = []
        has_output = False
        try:
            self.log("Opening image %s" % self.load_path)
            with open(self.load_path, 'rb') as image_tar:
                self.log("Loading image from %s" % self.load_path)
                output = self.client.load_image(image_tar)
                if output is not None:
                    # Old versions of Docker SDK of Python (before version 2.5.0) do not return anything.
                    # (See https://github.com/docker/docker-py/commit/7139e2d8f1ea82340417add02090bfaf7794f159)
                    # Note that before that commit, something else than None was returned, but that was also
                    # only introduced in a commit that first appeared in 2.5.0 (see
                    # https://github.com/docker/docker-py/commit/9e793806ff79559c3bc591d8c52a3bbe3cdb7350).
                    # So the above check works for every released version of Docker SDK for Python.
                    has_output = True
                    for line in output:
                        self.log(line, pretty_print=True)
                        self._extract_output_line(line, load_output)
                else:
                    if LooseVersion(docker_version) < LooseVersion('2.5.0'):
                        self.client.module.warn(
                            'The installed version of the Docker SDK for Python does not return the loading results'
                            ' from the Docker daemon. Therefore, we cannot verify whether the expected image was'
                            ' loaded, whether multiple images where loaded, or whether the load actually succeeded.'
                            ' If you are not stuck with Python 2.6, *please* upgrade to a version newer than 2.5.0'
                            ' (2.5.0 was released in August 2017).')
                    else:
                        self.client.module.warn(
                            'The API version of your Docker daemon is < 1.23, which does not return the image'
                            ' loading result from the Docker daemon. Therefore, we cannot verify whether the'
                            ' expected image was loaded, whether multiple images where loaded, or whether the load'
                            ' actually succeeded. You should consider upgrading your Docker daemon.'
                        )
        except EnvironmentError as exc:
            if exc.errno == errno.ENOENT:
                self.client.fail("Error opening image %s - %s" %
                                 (self.load_path, to_native(exc)))
            self.client.fail("Error loading image %s - %s" %
                             (self.name, to_native(exc)),
                             stdout='\n'.join(load_output))
        except Exception as exc:
            self.client.fail("Error loading image %s - %s" %
                             (self.name, to_native(exc)),
                             stdout='\n'.join(load_output))

        # Collect loaded images
        if has_output:
            # We can only do this when we actually got some output from Docker daemon
            loaded_images = set()
            loaded_image_ids = set()
            for line in load_output:
                if line.startswith('Loaded image:'):
                    loaded_images.add(line[len('Loaded image:'):].strip())
                if line.startswith('Loaded image ID:'):
                    loaded_image_ids.add(
                        line[len('Loaded image ID:'):].strip().lower())

            if not loaded_images and not loaded_image_ids:
                self.client.fail(
                    "Detected no loaded images. Archive potentially corrupt?",
                    stdout='\n'.join(load_output))

            if is_image_name_id(self.name):
                expected_image = self.name.lower()
                found_image = expected_image not in loaded_image_ids
            else:
                expected_image = '%s:%s' % (self.name, self.tag)
                found_image = expected_image not in loaded_images
            if found_image:
                self.client.fail(
                    "The archive did not contain image '%s'. Instead, found %s."
                    % (expected_image, ', '.join(
                        sorted(["'%s'" % image for image in loaded_images] +
                               list(loaded_image_ids)))),
                    stdout='\n'.join(load_output))
            loaded_images.remove(expected_image)

            if loaded_images:
                self.client.module.warn(
                    "The archive contained more images than specified: %s" %
                    (', '.join(
                        sorted(["'%s'" % image for image in loaded_images] +
                               list(loaded_image_ids))), ))

        if is_image_name_id(self.name):
            return self.client.find_image_by_id(self.name,
                                                accept_missing_image=True)
        else:
            return self.client.find_image(self.name, self.tag)
Пример #11
0
    def present(self):
        '''
        Handles state = 'present', which includes building, loading or pulling an image,
        depending on user provided parameters.

        :returns None
        '''
        if is_image_name_id(self.name):
            image = self.client.find_image_by_id(self.name,
                                                 accept_missing_image=True)
        else:
            image = self.client.find_image(name=self.name, tag=self.tag)

        if not image or self.force_source:
            if self.source == 'build':
                if is_image_name_id(self.name):
                    self.fail(
                        "Image name must not be an image ID for source=build; got: %s"
                        % self.name)

                # Build the image
                if not os.path.isdir(self.build_path):
                    self.fail(
                        "Requested build path %s could not be found or you do not have access."
                        % self.build_path)
                image_name = self.name
                if self.tag:
                    image_name = "%s:%s" % (self.name, self.tag)
                self.log("Building image %s" % image_name)
                self.results['actions'].append("Built image %s from %s" %
                                               (image_name, self.build_path))
                self.results['changed'] = True
                if not self.check_mode:
                    self.results.update(self.build_image())

            elif self.source == 'load':
                # Load the image from an archive
                if not os.path.isfile(self.load_path):
                    self.fail(
                        "Error loading image %s. Specified path %s does not exist."
                        % (self.name, self.load_path))
                image_name = self.name
                if self.tag and not is_image_name_id(image_name):
                    image_name = "%s:%s" % (self.name, self.tag)
                self.results['actions'].append("Loaded image %s from %s" %
                                               (image_name, self.load_path))
                self.results['changed'] = True
                if not self.check_mode:
                    self.results['image'] = self.load_image()
            elif self.source == 'pull':
                if is_image_name_id(self.name):
                    self.fail(
                        "Image name must not be an image ID for source=pull; got: %s"
                        % self.name)

                # pull the image
                self.results['actions'].append('Pulled image %s:%s' %
                                               (self.name, self.tag))
                self.results['changed'] = True
                if not self.check_mode:
                    self.results['image'], dummy = self.client.pull_image(
                        self.name, tag=self.tag, platform=self.pull_platform)
            elif self.source == 'local':
                if image is None:
                    name = self.name
                    if self.tag and not is_image_name_id(name):
                        name = "%s:%s" % (self.name, self.tag)
                    self.client.fail('Cannot find the image %s locally.' %
                                     name)
            if not self.check_mode and image and image['Id'] == self.results[
                    'image']['Id']:
                self.results['changed'] = False
        else:
            self.results['image'] = image

        if self.archive_path:
            self.archive_image(self.name, self.tag)

        if self.push and not self.repository:
            self.push_image(self.name, self.tag)
        elif self.repository:
            self.tag_image(self.name,
                           self.tag,
                           self.repository,
                           push=self.push)