diff --git a/README.md b/README.md index 2a7a87ab..82f8f1b8 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ If you use the Ansible package and do not update collections independently, use - community.docker.docker_image_load: load Docker images from archives - community.docker.docker_image_pull: pull Docker images from registries - community.docker.docker_image_push: push Docker images to registries + - community.docker.docker_image_remove: remove Docker images - community.docker.docker_image_tag: tag Docker images with new names and/or tags - community.docker.docker_login: log in and out to/from registries - community.docker.docker_network: manage Docker networks diff --git a/meta/runtime.yml b/meta/runtime.yml index 54522ea6..50e72ea0 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -18,6 +18,7 @@ action_groups: - docker_image_load - docker_image_pull - docker_image_push + - docker_image_remove - docker_image_tag - docker_login - docker_network diff --git a/plugins/modules/docker_image.py b/plugins/modules/docker_image.py index 7ac753a6..855a712c 100644 --- a/plugins/modules/docker_image.py +++ b/plugins/modules/docker_image.py @@ -254,6 +254,8 @@ seealso: - module: community.docker.docker_image_info - module: community.docker.docker_image_load - module: community.docker.docker_image_pull + - module: community.docker.docker_image_push + - module: community.docker.docker_image_remove - module: community.docker.docker_image_tag ''' diff --git a/plugins/modules/docker_image_load.py b/plugins/modules/docker_image_load.py index 880ae4e4..5dc796e5 100644 --- a/plugins/modules/docker_image_load.py +++ b/plugins/modules/docker_image_load.py @@ -47,6 +47,11 @@ requirements: author: - Felix Fontein (@felixfontein) + +seealso: + - module: community.docker.docker_image_push + - module: community.docker.docker_image_remove + - module: community.docker.docker_image_tag ''' EXAMPLES = ''' diff --git a/plugins/modules/docker_image_pull.py b/plugins/modules/docker_image_pull.py index ef6eea87..a3d9858b 100644 --- a/plugins/modules/docker_image_pull.py +++ b/plugins/modules/docker_image_pull.py @@ -64,6 +64,11 @@ requirements: author: - Felix Fontein (@felixfontein) + +seealso: + - module: community.docker.docker_image_pull + - module: community.docker.docker_image_remove + - module: community.docker.docker_image_tag ''' EXAMPLES = ''' diff --git a/plugins/modules/docker_image_push.py b/plugins/modules/docker_image_push.py index 705bf25f..b5c2d35a 100644 --- a/plugins/modules/docker_image_push.py +++ b/plugins/modules/docker_image_push.py @@ -36,7 +36,7 @@ options: required: true tag: description: - - Used to select an image when pulling. Defaults to V(latest). + - Select which image to push. 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 @@ -48,6 +48,8 @@ author: - Felix Fontein (@felixfontein) seealso: + - module: community.docker.docker_image_pull + - module: community.docker.docker_image_remove - module: community.docker.docker_image_tag ''' diff --git a/plugins/modules/docker_image_remove.py b/plugins/modules/docker_image_remove.py new file mode 100644 index 00000000..73bb7a0c --- /dev/null +++ b/plugins/modules/docker_image_remove.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# +# Copyright 2016 Red Hat | Ansible +# 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 absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: docker_image_remove + +short_description: Remove Docker images + +description: + - Remove Docker images from the Docker daemon. + +extends_documentation_fragment: + - community.docker.docker.api_documentation + - community.docker.attributes + - community.docker.attributes.actiongroup_docker + +attributes: + check_mode: + support: full + diff_mode: + support: full + +options: + name: + description: + - "Image name. Name format will be one of: C(name), C(repository/name), C(registry_server:port/name). + When pushing or pulling an image the name can optionally include the tag by appending C(:tag_name)." + - Note that image IDs (hashes) can also be used. + type: str + required: true + tag: + description: + - Tag for the image name O(name) that is to be tagged. + - If O(name)'s format is C(name:tag), then the tag value from O(name) will take precedence. + type: str + default: latest + force: + description: + - Un-tag and remove all images matching the specified name. + type: bool + default: false + prune: + description: + - Delete untagged parent images. + type: bool + default: true + +requirements: + - "Docker API >= 1.25" + +author: + - Felix Fontein (@felixfontein) + +seealso: + - module: community.docker.docker_image_load + - module: community.docker.docker_image_pull + - module: community.docker.docker_image_tag +''' + +EXAMPLES = ''' + +- name: Remove an image + community.docker.docker_image_remove: + name: pacur/centos-7 +''' + +RETURN = ''' +image: + description: + - Image inspection results for the affected image before removal. + - Empty if the image was not found. + returned: success + type: dict + sample: {} +deleted: + description: + - The digests of the images that were deleted. + returned: success + type: list + elements: str + sample: [] +untagged: + description: + - The digests of the images that were untagged. + returned: success + type: list + elements: str + sample: [] +''' + +import traceback + +from ansible.module_utils.common.text.converters import to_native + +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, +) + +from ansible_collections.community.docker.plugins.module_utils._api.errors import DockerException, NotFound +from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import ( + parse_repository_tag, +) + + +class ImageRemover(DockerBaseClass): + + def __init__(self, client): + super(ImageRemover, self).__init__() + + self.client = client + self.check_mode = self.client.check_mode + self.diff = self.client.module._diff + + parameters = self.client.module.params + self.name = parameters['name'] + self.tag = parameters['tag'] + self.force = parameters['force'] + self.prune = parameters['prune'] + + if not is_valid_tag(self.tag, allow_empty=True): + self.fail('"{0}" is not a valid docker tag'.format(self.tag)) + + # 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 + + def fail(self, msg): + self.client.fail(msg) + + def get_diff_state(self, image): + if not image: + return dict(exists=False) + return dict( + exists=True, + id=image['Id'], + tags=sorted(image.get('RepoTags') or []), + digests=sorted(image.get('RepoDigests') or []), + ) + + def absent(self): + results = dict( + changed=False, + actions=[], + image={}, + deleted=[], + untagged=[], + ) + + 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 self.diff: + results['diff'] = dict(before=self.get_diff_state(image)) + + if not image: + if self.diff: + results['diff']['after'] = self.get_diff_state(image) + return results + + results['changed'] = True + results['actions'].append("Removed image %s" % (name)) + results['image'] = image + + if not self.check_mode: + try: + res = self.client.delete_json('/images/{0}', name, params={'force': self.force, 'noprune': not self.prune}) + except NotFound: + # If the image vanished while we were trying to remove it, don't fail + res = [] + except Exception as exc: + self.fail("Error removing image %s - %s" % (name, to_native(exc))) + + for entry in res: + if entry.get('Untagged'): + results['untagged'].append(entry['Untagged']) + if entry.get('Deleted'): + results['deleted'].append(entry['Deleted']) + + results['untagged'] = sorted(results['untagged']) + results['deleted'] = sorted(results['deleted']) + + if self.diff: + image_after = self.client.find_image_by_id(image['Id'], accept_missing_image=True) + results['diff']['after'] = self.get_diff_state(image_after) + + elif is_image_name_id(name): + results['deleted'].append(image['Id']) + results['untagged'] = sorted((image.get('RepoTags') or []) + (image.get('RepoDigests') or [])) + if not self.force and results['untagged']: + self.fail('Cannot delete image by ID that is still in use - use force=true') + if self.diff: + results['diff']['after'] = self.get_diff_state({}) + + elif is_image_name_id(self.tag): + results['untagged'].append(name) + if len(image.get('RepoTags') or []) < 1 and len(image.get('RepoDigests') or []) < 2: + results['deleted'].append(image['Id']) + if self.diff: + results['diff']['after'] = self.get_diff_state(image) + try: + results['diff']['after']['digests'].remove(name) + except ValueError: + pass + + else: + results['untagged'].append(name) + if len(image.get('RepoTags') or []) < 2 and len(image.get('RepoDigests') or []) < 1: + results['deleted'].append(image['Id']) + if self.diff: + results['diff']['after'] = self.get_diff_state(image) + try: + results['diff']['after']['tags'].remove(name) + except ValueError: + pass + + return results + + +def main(): + argument_spec = dict( + name=dict(type='str', required=True), + tag=dict(type='str', default='latest'), + force=dict(type='bool', default=False), + prune=dict(type='bool', default=True), + ) + + client = AnsibleDockerClient( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + try: + results = ImageRemover(client).absent() + client.module.exit_json(**results) + except DockerException as e: + client.fail('An unexpected Docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc()) + except RequestException as e: + client.fail( + 'An unexpected requests error occurred when trying to talk to the Docker daemon: {0}'.format(to_native(e)), + exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/docker_image_tag.py b/plugins/modules/docker_image_tag.py index 49ba0b7b..bfe8e548 100644 --- a/plugins/modules/docker_image_tag.py +++ b/plugins/modules/docker_image_tag.py @@ -70,6 +70,7 @@ author: seealso: - module: community.docker.docker_image_push + - module: community.docker.docker_image_remove ''' EXAMPLES = ''' diff --git a/tests/integration/targets/docker_container/tasks/main.yml b/tests/integration/targets/docker_container/tasks/main.yml index 9911452f..6f8cead0 100644 --- a/tests/integration/targets/docker_container/tasks/main.yml +++ b/tests/integration/targets/docker_container/tasks/main.yml @@ -47,9 +47,8 @@ with_items: "{{ cnames }}" diff: false - name: "Make sure all images are removed" - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent with_items: "{{ inames }}" - name: "Make sure all networks are removed" docker_network: diff --git a/tests/integration/targets/docker_container/tasks/tests/comparisons.yml b/tests/integration/targets/docker_container/tasks/tests/comparisons.yml index 54f0d4a6..c37a0c4d 100644 --- a/tests/integration/targets/docker_container/tasks/tests/comparisons.yml +++ b/tests/integration/targets/docker_container/tasks/tests/comparisons.yml @@ -384,9 +384,8 @@ - name: Pull {{ docker_test_image_hello_world }} image to make sure wildcard_2 test succeeds # If the image isn't there, it will pull it and return 'changed'. - docker_image: + docker_image_pull: name: "{{ docker_test_image_hello_world }}" - source: pull - name: wildcard docker_container: diff --git a/tests/integration/targets/docker_container/tasks/tests/image-ids.yml b/tests/integration/targets/docker_container/tasks/tests/image-ids.yml index 76270c68..4dfdb02a 100644 --- a/tests/integration/targets/docker_container/tasks/tests/image-ids.yml +++ b/tests/integration/targets/docker_container/tasks/tests/image-ids.yml @@ -11,9 +11,8 @@ cnames: "{{ cnames + [cname] }}" - name: Pull images - docker_image: + docker_image_pull: name: "{{ image }}" - source: pull loop: - "{{ docker_test_image_hello_world }}" - "{{ docker_test_image_alpine }}" @@ -69,10 +68,9 @@ - name: Untag image # Image will not be deleted since the container still uses it - docker_image: + docker_image_remove: name: "{{ docker_test_image_alpine }}" - force_absent: true - state: absent + force: true - name: Create container with {{ docker_test_image_alpine }} image via name (check mode, will pull, same image) docker_container: diff --git a/tests/integration/targets/docker_container/tasks/tests/options.yml b/tests/integration/targets/docker_container/tasks/tests/options.yml index 84ecd69f..9766d068 100644 --- a/tests/integration/targets/docker_container/tasks/tests/options.yml +++ b/tests/integration/targets/docker_container/tasks/tests/options.yml @@ -2235,9 +2235,8 @@ - name: Pull images to make sure ignore_image test succeeds # If the image isn't there, it will pull it and return 'changed'. - docker_image: + docker_image_pull: name: "{{ item }}" - source: pull loop: - "{{ docker_test_image_hello_world }}" - "{{ docker_test_image_registry_nginx }}" @@ -2456,9 +2455,8 @@ diff: false - name: cleanup image - docker_image: + docker_image_remove: name: "{{ iname_labels }}" - state: absent diff: false - assert: @@ -2486,9 +2484,8 @@ - name: Pull images to make sure ignore_image test succeeds # If the image isn't there, it will pull it and return 'changed'. - docker_image: + docker_image_pull: name: "{{ item }}" - source: pull loop: - "{{ docker_test_image_hello_world }}" - "{{ docker_test_image_registry_nginx }}" @@ -2577,9 +2574,8 @@ inames: "{{ inames + [iname_name_mismatch] }}" - name: Tag hello world image (pulled earlier) with new name - docker_image: + docker_image_tag: name: "{{ docker_test_image_registry_nginx }}" - source: local repository: "{{ iname_name_mismatch }}:latest" - name: image_name_mismatch @@ -2617,9 +2613,8 @@ diff: false - name: Cleanup image - docker_image: + docker_image_remove: name: "{{ iname_name_mismatch }}" - state: absent diff: false - assert: @@ -3549,9 +3544,8 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau #################################################################### - name: Remove hello-world image - docker_image: + docker_image_remove: name: hello-world:latest - state: absent - name: platform docker_container: diff --git a/tests/integration/targets/docker_image_info/tasks/main.yml b/tests/integration/targets/docker_image_info/tasks/main.yml index 1e18d8c5..289a9cf1 100644 --- a/tests/integration/targets/docker_image_info/tasks/main.yml +++ b/tests/integration/targets/docker_image_info/tasks/main.yml @@ -10,9 +10,8 @@ - block: - name: Make sure image is not there - docker_image: + docker_image_remove: name: "{{ docker_test_image_alpine_different }}" - state: absent - name: Inspect a non-available image docker_image_info: diff --git a/tests/integration/targets/docker_image_load/tasks/test.yml b/tests/integration/targets/docker_image_load/tasks/test.yml index a56c9530..2e05174c 100644 --- a/tests/integration/targets/docker_image_load/tasks/test.yml +++ b/tests/integration/targets/docker_image_load/tasks/test.yml @@ -21,9 +21,8 @@ always: - name: "Make sure all images are removed" - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent with_items: "{{ inames }}" - name: "Make sure all containers are removed" docker_container: diff --git a/tests/integration/targets/docker_image_load/tasks/tests/basic.yml b/tests/integration/targets/docker_image_load/tasks/tests/basic.yml index 6b8ef710..42808770 100644 --- a/tests/integration/targets/docker_image_load/tasks/tests/basic.yml +++ b/tests/integration/targets/docker_image_load/tasks/tests/basic.yml @@ -42,10 +42,9 @@ # All images by IDs - name: Remove all images - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent - force_absent: true + force: true loop: "{{ all_images }}" ignore_errors: true register: remove_all_images @@ -66,10 +65,9 @@ when: remove_all_images is failed - name: Remove all images (after pruning) - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent - force_absent: true + force: true loop: "{{ all_images }}" when: remove_all_images is failed @@ -106,9 +104,8 @@ # Mixed images and IDs - name: Remove all images - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent loop: "{{ all_images }}" - name: Load all images (mixed images and IDs) @@ -136,9 +133,8 @@ # Same image twice - name: Remove all images - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent loop: "{{ all_images }}" - name: Load all images (same image twice) @@ -161,9 +157,8 @@ # Single image by ID - name: Remove all images - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent loop: "{{ all_images }}" - name: Load all images (single image by ID) @@ -196,9 +191,8 @@ # All images by names - name: Remove all images - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent loop: "{{ all_images }}" - name: Load all images (names) diff --git a/tests/integration/targets/docker_image_pull/tasks/test.yml b/tests/integration/targets/docker_image_pull/tasks/test.yml index a56c9530..2e05174c 100644 --- a/tests/integration/targets/docker_image_pull/tasks/test.yml +++ b/tests/integration/targets/docker_image_pull/tasks/test.yml @@ -21,9 +21,8 @@ always: - name: "Make sure all images are removed" - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent with_items: "{{ inames }}" - name: "Make sure all containers are removed" docker_container: diff --git a/tests/integration/targets/docker_image_pull/tasks/tests/basic.yml b/tests/integration/targets/docker_image_pull/tasks/tests/basic.yml index ad1e1738..23e1f092 100644 --- a/tests/integration/targets/docker_image_pull/tasks/tests/basic.yml +++ b/tests/integration/targets/docker_image_pull/tasks/tests/basic.yml @@ -8,10 +8,9 @@ image_name: "{{ docker_test_image_hello_world_platform }}" block: - name: Make sure image is not there - docker_image: + docker_image_remove: name: "{{ image_name }}" - state: absent - force_absent: true + force: true - name: Pull image (check mode) docker_image_pull: @@ -170,10 +169,9 @@ always: - name: cleanup - docker_image: + docker_image_remove: name: "{{ image_name }}" - state: absent - force_absent: true + force: true - name: Pull image ID (must fail) docker_image_pull: diff --git a/tests/integration/targets/docker_image_pull/tasks/tests/image-ids.yml b/tests/integration/targets/docker_image_pull/tasks/tests/image-ids.yml index 91a07e87..dd092a8b 100644 --- a/tests/integration/targets/docker_image_pull/tasks/tests/image-ids.yml +++ b/tests/integration/targets/docker_image_pull/tasks/tests/image-ids.yml @@ -6,10 +6,9 @@ - name: Image ID pull tests block: - name: Make sure images are not there - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent - force_absent: true + force: true loop: - "sha256:{{ docker_test_image_digest_v1_image_id }}" - "sha256:{{ docker_test_image_digest_v2_image_id }}" @@ -82,10 +81,9 @@ always: - name: cleanup - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent - force_absent: true + force: true loop: - "sha256:{{ docker_test_image_digest_v1_image_id }}" - "sha256:{{ docker_test_image_digest_v2_image_id }}" diff --git a/tests/integration/targets/docker_image_pull/tasks/tests/registry.yml b/tests/integration/targets/docker_image_pull/tasks/tests/registry.yml index c7197c42..2edc3b62 100644 --- a/tests/integration/targets/docker_image_pull/tasks/tests/registry.yml +++ b/tests/integration/targets/docker_image_pull/tasks/tests/registry.yml @@ -19,10 +19,9 @@ inames: "{{ inames + [iname, hello_world_image_base ~ ':latest'] }}" - name: Make sure image is not there - docker_image: + docker_image_remove: name: "{{ hello_world_image_base }}:latest" - state: absent - force_absent: true + force: true - name: Make sure we have {{ docker_test_image_hello_world }} docker_image_pull: @@ -45,10 +44,9 @@ register: facts_1 - name: Make sure image is not there - docker_image: + docker_image_remove: name: "{{ hello_world_image_base }}:latest" - state: absent - force_absent: true + force: true - name: Get facts of local image (not there) docker_image_info: diff --git a/tests/integration/targets/docker_image_push/tasks/test.yml b/tests/integration/targets/docker_image_push/tasks/test.yml index a56c9530..2e05174c 100644 --- a/tests/integration/targets/docker_image_push/tasks/test.yml +++ b/tests/integration/targets/docker_image_push/tasks/test.yml @@ -21,9 +21,8 @@ always: - name: "Make sure all images are removed" - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent with_items: "{{ inames }}" - name: "Make sure all containers are removed" docker_container: diff --git a/tests/integration/targets/docker_image_push/tasks/tests/basic.yml b/tests/integration/targets/docker_image_push/tasks/tests/basic.yml index e5cc1b46..15ed8744 100644 --- a/tests/integration/targets/docker_image_push/tasks/tests/basic.yml +++ b/tests/integration/targets/docker_image_push/tasks/tests/basic.yml @@ -7,9 +7,8 @@ image_name: registry.example.com:5000/foo/bar:baz block: - name: Make sure image is not present - docker_image: + docker_image_remove: name: "{{ image_name }}" - state: absent - name: Push non-existing image (must fail) docker_image_push: diff --git a/tests/integration/targets/docker_image_remove/aliases b/tests/integration/targets/docker_image_remove/aliases new file mode 100644 index 00000000..2e1acc0a --- /dev/null +++ b/tests/integration/targets/docker_image_remove/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# 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 + +azp/4 +destructive diff --git a/tests/integration/targets/docker_image_remove/meta/main.yml b/tests/integration/targets/docker_image_remove/meta/main.yml new file mode 100644 index 00000000..471ddd41 --- /dev/null +++ b/tests/integration/targets/docker_image_remove/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# 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 + +dependencies: + - setup_docker + - setup_docker_python_deps diff --git a/tests/integration/targets/docker_image_remove/tasks/main.yml b/tests/integration/targets/docker_image_remove/tasks/main.yml new file mode 100644 index 00000000..afa72da8 --- /dev/null +++ b/tests/integration/targets/docker_image_remove/tasks/main.yml @@ -0,0 +1,296 @@ +--- +# Copyright (c) Ansible Project +# 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 + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Pick image prefix + set_fact: + iname_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" + + - name: Define image names + set_fact: + image: "{{ docker_test_image_hello_world }}" + image_id: "{{ docker_test_image_hello_world_image_id }}" + image_names: + - "{{ iname_prefix }}-tagged-1:latest" + - "{{ iname_prefix }}-tagged-1:foo" + - "{{ iname_prefix }}-tagged-1:bar" + + - name: Remove image complete + docker_image_remove: + name: "{{ image_id }}" + force: true + + - name: Remove tagged images + docker_image_remove: + name: "{{ item }}" + loop: "{{ image_names }}" + + - name: Make sure image we work with is there + docker_image_pull: + name: "{{ image }}" + register: pulled_image + diff: true + + - name: Remove tagged image (not there, check mode) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:latest" + register: remove_1_check + check_mode: true + diff: true + + - name: Remove tagged image (not there) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:latest" + register: remove_1 + diff: true + + - name: Inspect image + docker_image_info: + name: "{{ iname_prefix }}-tagged-1:latest" + register: info_1 + + - assert: + that: + - remove_1_check is not changed + - remove_1_check.diff == remove_1.diff + - remove_1 is not changed + - remove_1.diff.before.exists is false + - remove_1.diff.after.exists is false + - remove_1.deleted | length == 0 + - remove_1.untagged | length == 0 + - remove_1_check.deleted == remove_1.deleted + - remove_1_check.untagged == remove_1.untagged + - info_1.images | length == 0 + + - name: Tag image 1 + docker_image_tag: + name: "{{ image }}" + repository: + - "{{ iname_prefix }}-tagged-1:latest" + - "{{ iname_prefix }}-tagged-1:foo" + - "{{ iname_prefix }}-tagged-1:bar" + + - name: Remove tagged image (check mode) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:latest" + register: remove_2_check + check_mode: true + diff: true + + - name: Remove tagged image + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:latest" + register: remove_2 + diff: true + + - name: Inspect image + docker_image_info: + name: "{{ iname_prefix }}-tagged-1:latest" + register: info_2 + + - assert: + that: + - remove_2_check is changed + - remove_2_check.diff == remove_2.diff + - remove_2 is changed + - remove_2.diff.before.id == pulled_image.image.Id + - remove_2.diff.before.tags | length == 4 + - remove_2.diff.before.digests | length == 1 + - remove_2.diff.after.id == pulled_image.image.Id + - remove_2.diff.after.tags | length == 3 + - remove_2.diff.after.digests | length == 1 + - remove_2.deleted | length == 0 + - remove_2.untagged | length == 1 + - remove_2.untagged[0] == iname_prefix ~ '-tagged-1:latest' + - remove_2_check.deleted == remove_2.deleted + - remove_2_check.untagged == remove_2.untagged + - info_2.images | length == 0 + + - name: Remove tagged image (idempotent, check mode) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:latest" + register: remove_3_check + check_mode: true + diff: true + + - name: Remove tagged image (idempotent) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:latest" + register: remove_3 + diff: true + + - assert: + that: + - remove_3_check is not changed + - remove_3_check.diff == remove_3.diff + - remove_3 is not changed + - remove_3.diff.before.exists is false + - remove_3.diff.after.exists is false + - remove_3.deleted | length == 0 + - remove_3.untagged | length == 0 + - remove_3_check.deleted == remove_3.deleted + - remove_3_check.untagged == remove_3.untagged + + - name: Inspect image with tag foo and bar + docker_image_info: + name: + - "{{ iname_prefix }}-tagged-1:foo" + - "{{ iname_prefix }}-tagged-1:bar" + register: info_3 + + - name: Remove tagged image (force, check mode) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:foo" + force: true + register: remove_4_check + check_mode: true + diff: true + + - name: Remove tagged image (force) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:foo" + force: true + register: remove_4 + diff: true + + - name: Inspect image with tag foo and bar + docker_image_info: + name: + - "{{ iname_prefix }}-tagged-1:foo" + - "{{ iname_prefix }}-tagged-1:bar" + register: info_4 + + - assert: + that: + - remove_4_check is changed + - remove_4_check.diff == remove_4.diff + - remove_4 is changed + - remove_4.diff.before.id == pulled_image.image.Id + - remove_4.diff.before.tags | length == 3 + - remove_4.diff.before.digests | length == 1 + - remove_4.diff.after.id == pulled_image.image.Id + - remove_4.diff.after.tags | length == 2 + - remove_4.diff.after.digests | length == 1 + - remove_4.deleted | length == 0 + - remove_4.untagged | length == 1 + - remove_4.untagged[0] == iname_prefix ~ '-tagged-1:foo' + - remove_4_check.deleted == remove_4.deleted + - remove_4_check.untagged == remove_4.untagged + - info_3.images | length == 2 + - info_3.images[0].Id == pulled_image.image.Id + - info_3.images[1].Id == pulled_image.image.Id + - info_4.images | length == 1 + - info_4.images[0].Id == pulled_image.image.Id + + - name: Remove image ID (force, idempotent, check mode) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:foo" + force: true + register: remove_5_check + check_mode: true + diff: true + + - name: Remove image ID (force, idempotent) + docker_image_remove: + name: "{{ iname_prefix }}-tagged-1:foo" + force: true + register: remove_5 + diff: true + + - assert: + that: + - remove_5_check is not changed + - remove_5_check.diff == remove_5.diff + - remove_5 is not changed + - remove_5.diff.before.exists is false + - remove_5.diff.after.exists is false + - remove_5.deleted | length == 0 + - remove_5.untagged | length == 0 + - remove_5_check.deleted == remove_5.deleted + - remove_5_check.untagged == remove_5.untagged + + - name: Remove image ID (force, check mode) + docker_image_remove: + name: "{{ pulled_image.image.Id }}" + force: true + register: remove_6_check + check_mode: true + diff: true + + - name: Remove image ID (force) + docker_image_remove: + name: "{{ pulled_image.image.Id }}" + force: true + register: remove_6 + diff: true + + - name: Inspect image with tag foo and bar + docker_image_info: + name: + - "{{ iname_prefix }}-tagged-1:foo" + - "{{ iname_prefix }}-tagged-1:bar" + register: info_5 + + - assert: + that: + - remove_6_check is changed + - remove_6_check.diff == remove_6.diff + - remove_6 is changed + - remove_6.diff.before.id == pulled_image.image.Id + - remove_6.diff.before.tags | length == 2 + - remove_6.diff.before.digests | length == 1 + - remove_6.diff.after.exists is false + - remove_6.deleted | length > 1 + - pulled_image.image.Id in remove_6.deleted + - remove_6.untagged | length == 3 + - (iname_prefix ~ '-tagged-1:bar') in remove_6.untagged + - image in remove_6.untagged + - remove_6_check.deleted | length == 1 + - remove_6_check.deleted[0] == pulled_image.image.Id + - remove_6_check.untagged == remove_6.untagged + - info_5.images | length == 0 + + - name: Remove image ID (force, idempotent, check mode) + docker_image_remove: + name: "{{ pulled_image.image.Id }}" + force: true + register: remove_7_check + check_mode: true + diff: true + + - name: Remove image ID (force, idempotent) + docker_image_remove: + name: "{{ pulled_image.image.Id }}" + force: true + register: remove_7 + diff: true + + - assert: + that: + - remove_7_check is not changed + - remove_7_check.diff == remove_7.diff + - remove_7 is not changed + - remove_7.diff.before.exists is false + - remove_7.diff.after.exists is false + - remove_7.deleted | length == 0 + - remove_7.untagged | length == 0 + - remove_7_check.deleted == remove_7.deleted + - remove_7_check.untagged == remove_7.untagged + + always: + - name: Remove tagged images + docker_image_remove: + name: "{{ item }}" + loop: "{{ image_names }}" + + when: docker_api_version is version('1.25', '>=') + +- fail: msg="Too old docker / docker-py version to run docker_image_info tests!" + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/tests/integration/targets/docker_image_tag/tasks/main.yml b/tests/integration/targets/docker_image_tag/tasks/main.yml index 8d479ae4..32791748 100644 --- a/tests/integration/targets/docker_image_tag/tasks/main.yml +++ b/tests/integration/targets/docker_image_tag/tasks/main.yml @@ -35,10 +35,8 @@ diff: true - name: Remove tagged images - docker_image: + docker_image_remove: name: "{{ item }}" - source: local - state: absent loop: "{{ image_names }}" - name: Tag image 1 (check mode) @@ -394,10 +392,8 @@ always: - name: Remove tagged images - docker_image: + docker_image_remove: name: "{{ item }}" - source: local - state: absent loop: "{{ image_names }}" when: docker_api_version is version('1.25', '>=') diff --git a/tests/integration/targets/setup_docker/vars/main.yml b/tests/integration/targets/setup_docker/vars/main.yml index 328add81..1859bdfe 100644 --- a/tests/integration/targets/setup_docker/vars/main.yml +++ b/tests/integration/targets/setup_docker/vars/main.yml @@ -9,6 +9,7 @@ docker_test_image_digest_v2: ee44b399df993016003bf5466bd3eeb221305e9d0fa831606bc docker_test_image_digest_v2_image_id: dc3bacd8b5ea796cea5d6070c8f145df9076f26a6bc1c8981fd5b176d37de843 docker_test_image_digest_base: quay.io/ansible/docker-test-containers docker_test_image_hello_world: quay.io/ansible/docker-test-containers:hello-world +docker_test_image_hello_world_image_id: sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b docker_test_image_hello_world_base: quay.io/ansible/docker-test-containers docker_test_image_hello_world_platform: docker.io/library/hello-world:latest docker_test_image_busybox: quay.io/ansible/docker-test-containers:busybox diff --git a/tests/integration/targets/setup_docker_registry/handlers/cleanup.yml b/tests/integration/targets/setup_docker_registry/handlers/cleanup.yml index 0a1f363c..6833be48 100644 --- a/tests/integration/targets/setup_docker_registry/handlers/cleanup.yml +++ b/tests/integration/targets/setup_docker_registry/handlers/cleanup.yml @@ -4,9 +4,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later - name: "Make sure all images are removed" - docker_image: + docker_image_remove: name: "{{ item }}" - state: absent with_items: "{{ docker_registry_setup_inames }}" - name: "Get registry logs"