diff --git a/changelogs/fragments/413-docker-api.yml b/changelogs/fragments/413-docker-api.yml new file mode 100644 index 00000000..608ca549 --- /dev/null +++ b/changelogs/fragments/413-docker-api.yml @@ -0,0 +1,4 @@ +major_changes: + - "docker_containers inventory plugin - no longer uses the Docker SDK for Python. It requires ``requests`` to be installed, + and depending on the features used has some more requirements. If the Docker SDK for Python is installed, + these requirements are likely met (https://github.com/ansible-collections/community.docker/pull/413)." diff --git a/plugins/inventory/docker_containers.py b/plugins/inventory/docker_containers.py index ea1cca57..78ff4bb6 100644 --- a/plugins/inventory/docker_containers.py +++ b/plugins/inventory/docker_containers.py @@ -17,12 +17,9 @@ short_description: Ansible dynamic inventory plugin for Docker containers. version_added: 1.1.0 author: - Felix Fontein (@felixfontein) -requirements: - - L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0 extends_documentation_fragment: - ansible.builtin.constructed - - community.docker.docker - - community.docker.docker.docker_py_1_documentation + - community.docker.docker.api_documentation description: - Reads inventories from the Docker API. - Uses a YAML configuration file that ends with C(docker.[yml|yaml]). @@ -154,23 +151,18 @@ from ansible.errors import AnsibleError from ansible.module_utils.common.text.converters import to_native from ansible.plugins.inventory import BaseInventoryPlugin, Constructable -from ansible_collections.community.docker.plugins.module_utils.common import ( +from ansible_collections.community.docker.plugins.module_utils.common_api import ( RequestException, ) from ansible_collections.community.docker.plugins.module_utils.util import ( DOCKER_COMMON_ARGS_VARS, ) -from ansible_collections.community.docker.plugins.plugin_utils.common import ( +from ansible_collections.community.docker.plugins.plugin_utils.common_api import ( AnsibleDockerClient, ) -try: - from docker.errors import DockerException, APIError -except Exception: - # missing Docker SDK for Python handled in ansible_collections.community.docker.plugins.module_utils.common - pass +from ansible_collections.community.docker.plugins.module_utils._api.errors import APIError, DockerException -MIN_DOCKER_PY = '1.7.0' MIN_DOCKER_API = None @@ -193,7 +185,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable): add_legacy_groups = self.get_option('add_legacy_groups') try: - containers = client.containers(all=True) + params = { + 'limit': -1, + 'all': 1, + 'size': 0, + 'trunc_cmd': 0, + 'since': None, + 'before': None, + } + containers = client.get_json('/containers/json', params=params) except APIError as exc: raise AnsibleError("Error listing containers: %s" % to_native(exc)) @@ -227,7 +227,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable): full_facts = dict() try: - inspect = client.inspect_container(id) + inspect = client.get_json('/containers/{0}/json', id) except APIError as exc: raise AnsibleError("Error inspecting container %s - %s" % (name, str(exc))) @@ -261,7 +261,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable): # Figure out ssh IP and Port try: # Lookup the public facing port Nat'ed to ssh port. - port = client.port(container, ssh_port)[0] + network_settings = inspect.get('NetworkSettings') or {} + port_settings = network_settings.get('Ports') or {} + port = port_settings.get('%d/tcp' % (ssh_port, ))[0] except (IndexError, AttributeError, TypeError): port = dict() @@ -330,7 +332,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable): path.endswith(('docker.yaml', 'docker.yml'))) def _create_client(self): - return AnsibleDockerClient(self, min_docker_version=MIN_DOCKER_PY, min_docker_api_version=MIN_DOCKER_API) + return AnsibleDockerClient(self, min_docker_api_version=MIN_DOCKER_API) def parse(self, inventory, loader, path, cache=True): super(InventoryModule, self).parse(inventory, loader, path, cache) @@ -340,9 +342,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable): self._populate(client) except DockerException as e: raise AnsibleError( - 'An unexpected docker error occurred: {0}'.format(e) + 'An unexpected Docker error occurred: {0}'.format(e) ) except RequestException as e: raise AnsibleError( - 'An unexpected requests error occurred when Docker SDK for Python tried to talk to the docker daemon: {0}'.format(e) + 'An unexpected requests error occurred when trying to talk to the Docker daemon: {0}'.format(e) ) diff --git a/tests/unit/plugins/inventory/test_docker_containers.py b/tests/unit/plugins/inventory/test_docker_containers.py index b729d9bd..46742dac 100644 --- a/tests/unit/plugins/inventory/test_docker_containers.py +++ b/tests/unit/plugins/inventory/test_docker_containers.py @@ -93,29 +93,22 @@ def create_get_option(options, default=False): class FakeClient(object): def __init__(self, *hosts): - self.hosts = dict() - self.list_reply = [] + self.get_results = {} + list_reply = [] for host in hosts: - self.list_reply.append({ + list_reply.append({ 'Id': host['Id'], 'Names': [host['Name']] if host['Name'] else [], 'Image': host['Config']['Image'], 'ImageId': host['Image'], }) - self.hosts[host['Name']] = host - self.hosts[host['Id']] = host + self.get_results['/containers/{0}/json'.format(host['Name'])] = host + self.get_results['/containers/{0}/json'.format(host['Id'])] = host + self.get_results['/containers/json'] = list_reply - def containers(self, all=False): - return list(self.list_reply) - - def inspect_container(self, id): - return self.hosts[id] - - def port(self, container, port): - host = self.hosts[container['Id']] - network_settings = host.get('NetworkSettings') or dict() - ports = network_settings.get('Ports') or dict() - return ports.get('{0}/tcp'.format(port)) or [] + def get_json(self, url, *param, **kwargs): + url = url.format(*param) + return self.get_results[url] def test_populate(inventory, mocker):