#!/usr/bin/python # # Copyright (c) 2023, Felix Fontein # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations DOCUMENTATION = r""" module: docker_image_pull short_description: Pull Docker images from registries version_added: 3.6.0 description: - Pulls a Docker image from a registry. extends_documentation_fragment: - community.docker.docker.api_documentation - community.docker.attributes - community.docker.attributes.actiongroup_docker attributes: check_mode: support: partial details: - When trying to pull an image with O(pull=always), the module assumes this is always changed in check mode. - When check mode is combined with diff mode, the pulled image's ID is always shown as V(unknown) in the diff. diff_mode: support: full idempotent: support: full options: name: description: - Image name. Name format must be one of V(name), V(repository/name), or V(registry_server:port/name). - The name can optionally include the tag by appending V(:tag_name), or it can contain a digest by appending V(@hash:digest). type: str required: true tag: description: - Used to select an image when pulling. Defaults to V(latest). - If O(name) parameter format is C(name:tag) or C(image@hash:digest), then O(tag) will be ignored. type: str default: latest platform: description: - Ask for this specific platform when pulling. type: str pull: description: - Determines when to pull an image. - If V(always), will always pull the image. - If V(not_present), will only pull the image if no image of the name exists on the current Docker daemon, or if O(platform) does not match. type: str choices: - always - not_present default: always requirements: - "Docker API >= 1.25" author: - Felix Fontein (@felixfontein) seealso: - module: community.docker.docker_image_pull - module: community.docker.docker_image_remove - module: community.docker.docker_image_tag """ EXAMPLES = r""" --- - name: Pull an image community.docker.docker_image_pull: name: pacur/centos-7 # Select platform for pulling. If not specified, will pull whatever docker prefers. platform: amd64 """ RETURN = r""" image: description: Image inspection results for the affected image. returned: success type: dict sample: {} """ import traceback from ansible_collections.community.docker.plugins.module_utils._api.errors import ( DockerException, ) from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import ( parse_repository_tag, ) from ansible_collections.community.docker.plugins.module_utils._platform import ( compare_platform_strings, compose_platform_string, normalize_platform_string, ) from ansible_collections.community.docker.plugins.module_utils.common_api import ( AnsibleDockerClient, RequestException, ) from ansible_collections.community.docker.plugins.module_utils.util import ( DockerBaseClass, is_image_name_id, is_valid_tag, ) def image_info(image): result = {} if image: result["id"] = image["Id"] else: result["exists"] = False return result class ImagePuller(DockerBaseClass): def __init__(self, client): super(ImagePuller, self).__init__() self.client = client self.check_mode = self.client.check_mode parameters = self.client.module.params self.name = parameters["name"] self.tag = parameters["tag"] self.platform = parameters["platform"] self.pull_mode = parameters["pull"] if is_image_name_id(self.name): self.client.fail("Cannot pull an image by ID") if not is_valid_tag(self.tag, allow_empty=True): self.client.fail(f'"{self.tag}" is not a valid docker tag!') # If name contains a tag, it takes precedence over tag parameter. repo, repo_tag = parse_repository_tag(self.name) if repo_tag: self.name = repo self.tag = repo_tag def pull(self): image = self.client.find_image(name=self.name, tag=self.tag) results = dict( changed=False, actions=[], image=image or {}, diff=dict(before=image_info(image), after=image_info(image)), ) if image and self.pull_mode == "not_present": if self.platform is None: return results host_info = self.client.info() wanted_platform = normalize_platform_string( self.platform, daemon_os=host_info.get("OSType"), daemon_arch=host_info.get("Architecture"), ) image_platform = compose_platform_string( os=image.get("Os"), arch=image.get("Architecture"), variant=image.get("Variant"), daemon_os=host_info.get("OSType"), daemon_arch=host_info.get("Architecture"), ) if compare_platform_strings(wanted_platform, image_platform): return results results["actions"].append(f"Pulled image {self.name}:{self.tag}") if self.check_mode: results["changed"] = True results["diff"]["after"] = image_info(dict(Id="unknown")) else: results["image"], not_changed = self.client.pull_image( self.name, tag=self.tag, platform=self.platform ) results["changed"] = not not_changed results["diff"]["after"] = image_info(results["image"]) return results def main(): argument_spec = dict( name=dict(type="str", required=True), tag=dict(type="str", default="latest"), platform=dict(type="str"), pull=dict(type="str", choices=["always", "not_present"], default="always"), ) option_minimal_versions = dict( platform=dict(docker_api_version="1.32"), ) client = AnsibleDockerClient( argument_spec=argument_spec, supports_check_mode=True, option_minimal_versions=option_minimal_versions, ) try: results = ImagePuller(client).pull() client.module.exit_json(**results) except DockerException as e: client.fail( f"An unexpected Docker error occurred: {e}", exception=traceback.format_exc(), ) except RequestException as e: client.fail( f"An unexpected requests error occurred when trying to talk to the Docker daemon: {e}", exception=traceback.format_exc(), ) if __name__ == "__main__": main()