diff --git a/lib/ansible/modules/cloud/docker/docker_prune.py b/lib/ansible/modules/cloud/docker/docker_prune.py new file mode 100644 index 00000000000..ceaff75e0fc --- /dev/null +++ b/lib/ansible/modules/cloud/docker/docker_prune.py @@ -0,0 +1,259 @@ +#!/usr/bin/python +# +# Copyright 2016 Red Hat | Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: docker_prune + +short_description: Allows to prune various docker objects + +description: + - Allows to run C(docker container prune), C(docker image prune), C(docker network prune) + and C(docker volume prune) via the Docker API. + +version_added: "2.8" + +options: + containers: + description: + - Whether to prune containers. + type: bool + default: no + containers_filters: + description: + - A dictionary of filter values used for selecting containers to delete. + - "For example, C(until: 24h)." + - See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/container_prune/#filtering) + for more information on possible filters. + type: dict + images: + description: + - Whether to prune images. + type: bool + default: no + images_filters: + description: + - A dictionary of filter values used for selecting images to delete. + - "For example, C(dangling: true)." + - See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/image_prune/#filtering) + for more information on possible filters. + type: dict + networks: + description: + - Whether to prune networks. + type: bool + default: no + networks_filters: + description: + - A dictionary of filter values used for selecting networks to delete. + - See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/network_prune/#filtering) + for more information on possible filters. + type: dict + volumes: + description: + - Whether to prune volumes. + type: bool + default: no + volumes_filters: + description: + - A dictionary of filter values used for selecting volumes to delete. + - See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/volume_prune/#filtering) + for more information on possible filters. + type: dict + builder_cache: + description: + - Whether to prune the builder cache. + - Requires version 3.3.0 of the Python Docker SDK or newer. + type: bool + default: no + +extends_documentation_fragment: + - docker + +author: + - "Felix Fontein (@felixfontein)" + +requirements: + - "python >= 2.6" + - "docker >= 2.1.0" + - "Please note that the L(docker-py,https://pypi.org/project/docker-py/) Python + module has been superseded by L(docker,https://pypi.org/project/docker/) + (see L(here,https://github.com/docker/docker-py/issues/1310) for details). + Version 2.1.0 or newer is only available with the C(docker) module." + - "Docker API >= 1.25" +''' + +EXAMPLES = ''' +- name: Prune containers older than 24h + docker_prune: + containers: yes + containers_filters: + # only consider containers created more than 24 hours ago + until: 24h + +- name: Prune everything + docker_prune: + containers: yes + images: yes + networks: yes + volumes: yes + builder_cache: yes +''' + +RETURN = ''' +# containers +containers: + description: + - List of IDs of deleted containers. + returned: C(containers) is C(true) + type: list + sample: '[]' +containers_space_reclaimed: + description: + - Amount of reclaimed disk space from container pruning in bytes. + returned: C(containers) is C(true) + type: int + sample: '0' + +# images +images: + description: + - List of IDs of deleted images. + returned: C(images) is C(true) + type: list + sample: '[]' +images_space_reclaimed: + description: + - Amount of reclaimed disk space from image pruning in bytes. + returned: C(images) is C(true) + type: int + sample: '0' + +# networks +networks: + description: + - List of IDs of deleted networks. + returned: C(networks) is C(true) + type: list + sample: '[]' + +# volumes +volumes: + description: + - List of IDs of deleted volumes. + returned: C(volumes) is C(true) + type: list + sample: '[]' +volumes_space_reclaimed: + description: + - Amount of reclaimed disk space from volumes pruning in bytes. + returned: C(volumes) is C(true) + type: int + sample: '0' + +# builder_cache +builder_cache_space_reclaimed: + description: + - Amount of reclaimed disk space from builder cache pruning in bytes. + returned: C(builder_cache) is C(true) + type: int + sample: '0' +''' + +from distutils.version import LooseVersion + +from ansible.module_utils.docker_common import AnsibleDockerClient + +try: + from ansible.module_utils.docker_common import docker_version +except Exception as dummy: + # missing docker-py handled in ansible.module_utils.docker + pass + + +def get_filters(module, name): + result = dict() + filters = module.params.get(name) + if filters is not None: + for k, v in filters.items(): + # Go doesn't like 'True' or 'False' + if v is True: + v = 'true' + elif v is False: + v = 'false' + else: + v = str(v) + result[str(k)] = v + return result + + +def main(): + argument_spec = dict( + containers=dict(type='bool', default=False), + containers_filters=dict(type='dict'), + images=dict(type='bool', default=False), + images_filters=dict(type='dict'), + networks=dict(type='bool', default=False), + networks_filters=dict(type='dict'), + volumes=dict(type='bool', default=False), + volumes_filters=dict(type='dict'), + builder_cache=dict(type='bool', default=False), + ) + + client = AnsibleDockerClient( + argument_spec=argument_spec, + # supports_check_mode=True, + min_docker_api_version='1.25', + min_docker_version='2.1.0', + ) + + # Version checks + if client.module.params['builder_cache'] and LooseVersion(docker_version) < LooseVersion('3.3.0'): + msg = "Error: docker version is %s. Minimum version required for builds option is %s. Use `pip install --upgrade docker` to upgrade." + client.module.fail(msg=(msg % (docker_version, ))) + + result = dict() + + if client.module.params['containers']: + filters = get_filters(client.module, 'containers_filters') + res = client.prune_containers(filters=filters) + result['containers'] = res.get('ContainersDeleted') or [] + result['containers_space_reclaimed'] = res['SpaceReclaimed'] + + if client.module.params['images']: + filters = get_filters(client.module, 'images_filters') + res = client.prune_images(filters=filters) + result['images'] = res.get('ImagesDeleted') or [] + result['images_space_reclaimed'] = res['SpaceReclaimed'] + + if client.module.params['networks']: + filters = get_filters(client.module, 'networks_filters') + res = client.prune_networks(filters=filters) + result['networks'] = res.get('NetworksDeleted') or [] + + if client.module.params['volumes']: + filters = get_filters(client.module, 'volumes_filters') + res = client.prune_volumes(filters=filters) + result['volumes'] = res.get('VolumesDeleted') or [] + result['volumes_space_reclaimed'] = res['SpaceReclaimed'] + + if client.module.params['builder_cache']: + res = client.prune_builds() + result['builder_cache_space_reclaimed'] = res['SpaceReclaimed'] + + client.module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/docker_prune/aliases b/test/integration/targets/docker_prune/aliases new file mode 100644 index 00000000000..2b3832dde58 --- /dev/null +++ b/test/integration/targets/docker_prune/aliases @@ -0,0 +1,4 @@ +shippable/posix/group2 +skip/osx +skip/freebsd +destructive diff --git a/test/integration/targets/docker_prune/meta/main.yml b/test/integration/targets/docker_prune/meta/main.yml new file mode 100644 index 00000000000..07da8c6ddae --- /dev/null +++ b/test/integration/targets/docker_prune/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_docker diff --git a/test/integration/targets/docker_prune/tasks/main.yml b/test/integration/targets/docker_prune/tasks/main.yml new file mode 100644 index 00000000000..9eed62afa1f --- /dev/null +++ b/test/integration/targets/docker_prune/tasks/main.yml @@ -0,0 +1,60 @@ +--- +- name: Create random names + set_fact: + cname: "{{ 'ansible-container-%0x' % ((2**32) | random) }}" + nname: "{{ 'ansible-network-%0x' % ((2**32) | random) }}" + vname: "{{ 'ansible-volume-%0x' % ((2**32) | random) }}" + +- block: + # Create objects to be pruned + - docker_container: + name: "{{ cname }}" + image: hello-world + state: present + register: container + - docker_network: + name: "{{ nname }}" + state: present + register: network + - docker_volume: + name: "{{ vname }}" + state: present + register: volume + + # Prune objects + - docker_prune: + containers: yes + images: yes + networks: yes + volumes: yes + builder_cache: yes + register: result + + # Analyze result + - debug: var=result + - assert: + that: + # containers + - container.ansible_facts.docker_container.Id in result.containers + - "'containers_space_reclaimed' in result" + # images + - "'images_space_reclaimed' in result" + # networks + - network.ansible_facts.docker_network.Name in result.networks + # volumes + - volume.ansible_facts.docker_volume.Name in result.volumes + - "'volumes_space_reclaimed' in result" + # builder_cache + - "'builder_cache_space_reclaimed' in result" + + # Test with filters + - docker_prune: + images: yes + images_filters: + dangling: true + register: result + + - debug: var=result + + # Skip for CentOS 6 + when: ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6