mirror of
https://github.com/ansible-collections/community.docker.git
synced 2025-12-17 12:28:55 +00:00
Docker inventory plugin (#61)
* Began with docker inventory plugin. * Linting. * Improve plugin, add basic unit tests. * Linting. * Add integration test. * Adjust tests to case that there are more containers. * There can be stopped containers. ci_coverage * docker -> docker_containers
This commit is contained in:
parent
2a0fb7d3ed
commit
9b131399ce
321
plugins/inventory/docker_containers.py
Normal file
321
plugins/inventory/docker_containers.py
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||||
|
# For the parts taken from the docker inventory script:
|
||||||
|
# Copyright (c) 2016, Paul Durivage <paul.durivage@gmail.com>
|
||||||
|
# Copyright (c) 2016, Chris Houseknecht <house@redhat.com>
|
||||||
|
# Copyright (c) 2016, James Tanner <jtanner@redhat.com>
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
name: docker_containers
|
||||||
|
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
|
||||||
|
description:
|
||||||
|
- Reads inventories from the Docker API.
|
||||||
|
- Uses a YAML configuration file that ends with C(docker.[yml|yaml]).
|
||||||
|
options:
|
||||||
|
plugin:
|
||||||
|
description:
|
||||||
|
- The name of this plugin, it should always be set to C(community.docker.docker_containers)
|
||||||
|
for this plugin to recognize it as it's own.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
choices: [ community.docker.docker_containers ]
|
||||||
|
|
||||||
|
connection_type:
|
||||||
|
description:
|
||||||
|
- Which connection type to use the containers.
|
||||||
|
- Default is to use SSH (C(ssh)). For this, the options I(default_ip) and
|
||||||
|
I(private_ssh_port) are used.
|
||||||
|
- Alternatively, C(docker-cli) selects the
|
||||||
|
R(docker connection plugin,ansible_collections.community.docker.docker_connection),
|
||||||
|
and C(docker-api) selects the
|
||||||
|
R(docker_api connection plugin,ansible_collections.community.docker.docker_api_connection).
|
||||||
|
type: str
|
||||||
|
default: docker-api
|
||||||
|
choices:
|
||||||
|
- ssh
|
||||||
|
- docker-cli
|
||||||
|
- docker-api
|
||||||
|
|
||||||
|
verbose_output:
|
||||||
|
description:
|
||||||
|
- Toggle to (not) include all available inspection metadata.
|
||||||
|
- Note that all top-level keys will be transformed to the format C(docker_xxx).
|
||||||
|
For example, C(HostConfig) is converted to C(docker_hostconfig).
|
||||||
|
- If this is C(false), these values can only be used during I(constructed), I(groups), and I(keyed_groups).
|
||||||
|
- The C(docker) inventory script always added these variables, so for compatibility set this to C(true).
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
|
||||||
|
default_ip:
|
||||||
|
description:
|
||||||
|
- The IP address to assign to ansible_host when the container's SSH port is mapped to interface
|
||||||
|
'0.0.0.0'.
|
||||||
|
- Only used if I(connection_type) is C(ssh).
|
||||||
|
type: str
|
||||||
|
default: 127.0.0.1
|
||||||
|
|
||||||
|
private_ssh_port:
|
||||||
|
description:
|
||||||
|
- The port containers use for SSH.
|
||||||
|
- Only used if I(connection_type) is C(ssh).
|
||||||
|
type: int
|
||||||
|
default: 22
|
||||||
|
|
||||||
|
add_legacy_groups:
|
||||||
|
description:
|
||||||
|
- "Add the same groups as the C(docker) inventory script does. These are the following:"
|
||||||
|
- "C(<container id>): contains the container of this ID."
|
||||||
|
- "C(<container name>): contains the container that has this name."
|
||||||
|
- "C(<container short id>): contains the containers that have this short ID (first 13 letters of ID)."
|
||||||
|
- "C(image_<image name>): contains the containers that have the image C(<image name>)."
|
||||||
|
- "C(stack_<stack name>): contains the containers that belong to the stack C(<stack name>)."
|
||||||
|
- "C(service_<service name>): contains the containers that belong to the service C(<service name>)"
|
||||||
|
- "C(<docker_host>): contains the containers which belong to the Docker daemon I(docker_host).
|
||||||
|
Useful if you run this plugin against multiple Docker daemons."
|
||||||
|
- "C(running): contains all containers that are running."
|
||||||
|
- "C(stopped): contains all containers that are not running."
|
||||||
|
- If this is not set to C(true), you should use keyed groups to add the containers to groups.
|
||||||
|
See the examples for how to do that.
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# Minimal example using local Docker daemon
|
||||||
|
plugin: community.docker.docker_containers
|
||||||
|
docker_host: unix://var/run/docker.sock
|
||||||
|
|
||||||
|
# Minimal example using remote Docker daemon
|
||||||
|
plugin: community.docker.docker_containers
|
||||||
|
docker_host: tcp://my-docker-host:2375
|
||||||
|
|
||||||
|
# Example using remote Docker daemon with unverified TLS
|
||||||
|
plugin: community.docker.docker_containers
|
||||||
|
docker_host: tcp://my-docker-host:2376
|
||||||
|
tls: true
|
||||||
|
|
||||||
|
# Example using remote Docker daemon with verified TLS and client certificate verification
|
||||||
|
plugin: community.docker.docker_containers
|
||||||
|
docker_host: tcp://my-docker-host:2376
|
||||||
|
validate_certs: true
|
||||||
|
ca_cert: /somewhere/ca.pem
|
||||||
|
client_key: /somewhere/key.pem
|
||||||
|
client_cert: /somewhere/cert.pem
|
||||||
|
|
||||||
|
# Example using constructed features to create groups
|
||||||
|
plugin: community.docker.docker_containers
|
||||||
|
docker_host: tcp://my-docker-host:2375
|
||||||
|
strict: false
|
||||||
|
keyed_groups:
|
||||||
|
# Add containers with primary network foo to a network_foo group
|
||||||
|
- prefix: network
|
||||||
|
key: 'docker_hostconfig.NetworkMode'
|
||||||
|
# Add Linux hosts to an os_linux group
|
||||||
|
- prefix: os
|
||||||
|
key: docker_platform
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
|
from ansible_collections.community.docker.plugins.module_utils.common import update_tls_hostname, get_connect_params
|
||||||
|
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
|
||||||
|
from ansible.parsing.utils.addresses import parse_address
|
||||||
|
|
||||||
|
from ansible_collections.community.docker.plugins.module_utils.common import (
|
||||||
|
RequestException,
|
||||||
|
)
|
||||||
|
from ansible_collections.community.docker.plugins.plugin_utils.common import (
|
||||||
|
AnsibleDockerClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from docker.errors import DockerException, APIError, NotFound
|
||||||
|
except Exception:
|
||||||
|
# missing Docker SDK for Python handled in ansible_collections.community.docker.plugins.module_utils.common
|
||||||
|
pass
|
||||||
|
|
||||||
|
MIN_DOCKER_PY = '1.7.0'
|
||||||
|
MIN_DOCKER_API = None
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||||
|
''' Host inventory parser for ansible using Docker daemon as source. '''
|
||||||
|
|
||||||
|
NAME = 'community.docker.docker_containers'
|
||||||
|
|
||||||
|
def _slugify(self, value):
|
||||||
|
return 'docker_%s' % (re.sub(r'[^\w-]', '_', value).lower().lstrip('_'))
|
||||||
|
|
||||||
|
def _populate(self, client):
|
||||||
|
strict = self.get_option('strict')
|
||||||
|
|
||||||
|
ssh_port = self.get_option('private_ssh_port')
|
||||||
|
default_ip = self.get_option('default_ip')
|
||||||
|
hostname = self.get_option('docker_host')
|
||||||
|
verbose_output = self.get_option('verbose_output')
|
||||||
|
connection_type = self.get_option('connection_type')
|
||||||
|
add_legacy_groups = self.get_option('add_legacy_groups')
|
||||||
|
|
||||||
|
try:
|
||||||
|
containers = client.containers(all=True)
|
||||||
|
except APIError as exc:
|
||||||
|
raise AnsibleError("Error listing containers: %s" % to_native(exc))
|
||||||
|
|
||||||
|
if add_legacy_groups:
|
||||||
|
self.inventory.add_group('running')
|
||||||
|
self.inventory.add_group('stopped')
|
||||||
|
|
||||||
|
for container in containers:
|
||||||
|
id = container.get('Id')
|
||||||
|
short_id = id[:13]
|
||||||
|
|
||||||
|
try:
|
||||||
|
name = container.get('Names', list())[0].lstrip('/')
|
||||||
|
full_name = name
|
||||||
|
except IndexError:
|
||||||
|
name = short_id
|
||||||
|
full_name = id
|
||||||
|
|
||||||
|
self.inventory.add_host(name)
|
||||||
|
facts = dict(
|
||||||
|
docker_name=name,
|
||||||
|
docker_short_id=short_id
|
||||||
|
)
|
||||||
|
full_facts = dict()
|
||||||
|
|
||||||
|
try:
|
||||||
|
inspect = client.inspect_container(id)
|
||||||
|
except APIError as exc:
|
||||||
|
raise AnsibleError("Error inspecting container %s - %s" % (name, str(exc)))
|
||||||
|
|
||||||
|
state = inspect.get('State') or dict()
|
||||||
|
config = inspect.get('Config') or dict()
|
||||||
|
labels = config.get('Labels') or dict()
|
||||||
|
|
||||||
|
running = state.get('Running')
|
||||||
|
|
||||||
|
# Add container to groups
|
||||||
|
image_name = config.get('Image')
|
||||||
|
if image_name and add_legacy_groups:
|
||||||
|
self.inventory.add_group('image_{0}'.format(image_name))
|
||||||
|
self.inventory.add_host(name, group='image_{0}'.format(image_name))
|
||||||
|
|
||||||
|
stack_name = labels.get('com.docker.stack.namespace')
|
||||||
|
if stack_name:
|
||||||
|
full_facts['docker_stack'] = stack_name
|
||||||
|
if add_legacy_groups:
|
||||||
|
self.inventory.add_group('stack_{0}'.format(stack_name))
|
||||||
|
self.inventory.add_host(name, group='stack_{0}'.format(stack_name))
|
||||||
|
|
||||||
|
service_name = labels.get('com.docker.swarm.service.name')
|
||||||
|
if service_name:
|
||||||
|
full_facts['docker_service'] = service_name
|
||||||
|
if add_legacy_groups:
|
||||||
|
self.inventory.add_group('service_{0}'.format(service_name))
|
||||||
|
self.inventory.add_host(name, group='service_{0}'.format(service_name))
|
||||||
|
|
||||||
|
if connection_type == 'ssh':
|
||||||
|
# Figure out ssh IP and Port
|
||||||
|
try:
|
||||||
|
# Lookup the public facing port Nat'ed to ssh port.
|
||||||
|
port = client.port(container, ssh_port)[0]
|
||||||
|
except (IndexError, AttributeError, TypeError):
|
||||||
|
port = dict()
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip = default_ip if port['HostIp'] == '0.0.0.0' else port['HostIp']
|
||||||
|
except KeyError:
|
||||||
|
ip = ''
|
||||||
|
|
||||||
|
facts.update(dict(
|
||||||
|
ansible_ssh_host=ip,
|
||||||
|
ansible_ssh_port=port.get('HostPort', 0),
|
||||||
|
))
|
||||||
|
elif connection_type == 'docker-cli':
|
||||||
|
facts.update(dict(
|
||||||
|
ansible_host=full_name,
|
||||||
|
ansible_connection='community.docker.docker',
|
||||||
|
))
|
||||||
|
elif connection_type == 'docker-api':
|
||||||
|
facts.update(dict(
|
||||||
|
ansible_host=full_name,
|
||||||
|
ansible_connection='community.docker.docker_api',
|
||||||
|
))
|
||||||
|
|
||||||
|
full_facts.update(facts)
|
||||||
|
for key, value in inspect.items():
|
||||||
|
fact_key = self._slugify(key)
|
||||||
|
full_facts[fact_key] = value
|
||||||
|
|
||||||
|
if verbose_output:
|
||||||
|
facts.update(full_facts)
|
||||||
|
|
||||||
|
for key, value in facts.items():
|
||||||
|
self.inventory.set_variable(name, key, value)
|
||||||
|
|
||||||
|
# Use constructed if applicable
|
||||||
|
# Composed variables
|
||||||
|
self._set_composite_vars(self.get_option('compose'), full_facts, name, strict=strict)
|
||||||
|
# Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group
|
||||||
|
self._add_host_to_composed_groups(self.get_option('groups'), full_facts, name, strict=strict)
|
||||||
|
# Create groups based on variable values and add the corresponding hosts to it
|
||||||
|
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), full_facts, name, strict=strict)
|
||||||
|
|
||||||
|
# We need to do this last since we also add a group called `name`.
|
||||||
|
# When we do this before a set_variable() call, the variables are assigned
|
||||||
|
# to the group, and not to the host.
|
||||||
|
if add_legacy_groups:
|
||||||
|
self.inventory.add_group(id)
|
||||||
|
self.inventory.add_host(name, group=id)
|
||||||
|
self.inventory.add_group(name)
|
||||||
|
self.inventory.add_host(name, group=name)
|
||||||
|
self.inventory.add_group(short_id)
|
||||||
|
self.inventory.add_host(name, group=short_id)
|
||||||
|
self.inventory.add_group(hostname)
|
||||||
|
self.inventory.add_host(name, group=hostname)
|
||||||
|
|
||||||
|
if running is True:
|
||||||
|
self.inventory.add_host(name, group='running')
|
||||||
|
else:
|
||||||
|
self.inventory.add_host(name, group='stopped')
|
||||||
|
|
||||||
|
def verify_file(self, path):
|
||||||
|
"""Return the possibly of a file being consumable by this plugin."""
|
||||||
|
return (
|
||||||
|
super(InventoryModule, self).verify_file(path) and
|
||||||
|
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)
|
||||||
|
|
||||||
|
def parse(self, inventory, loader, path, cache=True):
|
||||||
|
super(InventoryModule, self).parse(inventory, loader, path, cache)
|
||||||
|
self._read_config_data(path)
|
||||||
|
client = self._create_client()
|
||||||
|
try:
|
||||||
|
self._populate(client)
|
||||||
|
except DockerException as e:
|
||||||
|
raise AnsibleError(
|
||||||
|
'An unexpected docker error occurred: {0}'.format(e)
|
||||||
|
)
|
||||||
|
except RequestException as e:
|
||||||
|
raise AnsibleError(
|
||||||
|
'An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e)
|
||||||
|
)
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
shippable/posix/group4
|
||||||
|
destructive
|
||||||
|
needs/root
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
plugin: community.docker.docker_containers
|
||||||
|
docker_host: unix://var/run/docker.sock
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
plugin: community.docker.docker_containers
|
||||||
|
docker_host: unix://var/run/docker.sock
|
||||||
|
connection_type: ssh
|
||||||
|
verbose_output: true
|
||||||
|
add_legacy_groups: true
|
||||||
|
default_ip: 1.2.3.4
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
dependencies:
|
||||||
|
- setup_docker
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
- hosts: 127.0.0.1
|
||||||
|
connection: local
|
||||||
|
gather_facts: yes
|
||||||
|
tasks:
|
||||||
|
- name: remove docker containers
|
||||||
|
docker_container:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
force_kill: yes
|
||||||
|
loop:
|
||||||
|
- ansible-test-docker-inventory-container-1
|
||||||
|
- ansible-test-docker-inventory-container-2
|
||||||
|
|
||||||
|
- name: remove docker pagkages
|
||||||
|
action: "{{ ansible_facts.pkg_mgr }}"
|
||||||
|
args:
|
||||||
|
name:
|
||||||
|
- docker
|
||||||
|
- docker-ce
|
||||||
|
- docker-ce-cli
|
||||||
|
state: absent
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
- hosts: 127.0.0.1
|
||||||
|
connection: local
|
||||||
|
vars:
|
||||||
|
docker_skip_cleanup: yes
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Setup docker
|
||||||
|
import_role:
|
||||||
|
name: setup_docker
|
||||||
|
|
||||||
|
- name: Start containers
|
||||||
|
docker_container:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
image: "{{ docker_test_image_alpine }}"
|
||||||
|
state: started
|
||||||
|
command: '/bin/sh -c "sleep 10m"'
|
||||||
|
published_ports:
|
||||||
|
- 22/tcp
|
||||||
|
loop:
|
||||||
|
- name: ansible-test-docker-inventory-container-1
|
||||||
|
- name: ansible-test-docker-inventory-container-2
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
- hosts: 127.0.0.1
|
||||||
|
connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Show all groups
|
||||||
|
debug:
|
||||||
|
var: groups
|
||||||
|
- name: Make sure that the default groups are there, but no others
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- groups.all | length >= 2
|
||||||
|
- groups.ungrouped | length >= 2
|
||||||
|
- groups | length == 2
|
||||||
|
|
||||||
|
- hosts: all
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- when:
|
||||||
|
# When the integration tests are run inside a docker container, there
|
||||||
|
# will be other containers.
|
||||||
|
- inventory_hostname.startswith('ansible-test-docker-inventory-container-')
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: Run raw command
|
||||||
|
raw: ls /
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: Check whether we have some directories we expect in the output
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'bin' in output.stdout_lines"
|
||||||
|
- "'dev' in output.stdout_lines"
|
||||||
|
- "'lib' in output.stdout_lines"
|
||||||
|
- "'proc' in output.stdout_lines"
|
||||||
|
- "'sys' in output.stdout_lines"
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
- hosts: 127.0.0.1
|
||||||
|
connection: local # otherwise Ansible will complain that it cannot connect via ssh to 127.0.0.1:22
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Show all groups
|
||||||
|
debug:
|
||||||
|
var: groups
|
||||||
|
- name: Load variables
|
||||||
|
include_vars: ../../setup_docker/vars/main.yml
|
||||||
|
- name: Make sure that the expected groups are there
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- groups.all | length >= 2
|
||||||
|
- groups.ungrouped | length >= 0
|
||||||
|
- groups.running | length >= 2
|
||||||
|
- groups.stopped | length >= 0
|
||||||
|
- groups['image_' ~ docker_test_image_alpine] | length == 2
|
||||||
|
- groups['ansible-test-docker-inventory-container-1'] | length == 1
|
||||||
|
- groups['ansible-test-docker-inventory-container-2'] | length == 1
|
||||||
|
- groups['unix://var/run/docker.sock'] | length >= 2
|
||||||
|
- groups | length >= 12
|
||||||
|
# The four additional groups are IDs and short IDs of the containers.
|
||||||
|
# When the integration tests are run inside a docker container, there
|
||||||
|
# will be more groups (for the additional container(s)).
|
||||||
|
|
||||||
|
- hosts: all
|
||||||
|
# We don't really want to connect to the nodes, since we have no SSH daemon running on them
|
||||||
|
connection: local
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: "{{ ansible_playbook_python }}"
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Show all variables
|
||||||
|
debug:
|
||||||
|
var: hostvars[inventory_hostname]
|
||||||
|
- name: Make sure SSH is set up
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- ansible_ssh_host == '1.2.3.4'
|
||||||
|
- ansible_ssh_port == docker_networksettings.Ports['22/tcp'][0].HostPort
|
||||||
|
when:
|
||||||
|
# When the integration tests are run inside a docker container, there
|
||||||
|
# will be other containers.
|
||||||
|
- inventory_hostname.startswith('ansible-test-docker-inventory-container-')
|
||||||
22
tests/integration/targets/inventory_docker_containers/runme.sh
Executable file
22
tests/integration/targets/inventory_docker_containers/runme.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
[[ -n "$DEBUG" || -n "$ANSIBLE_DEBUG" ]] && set -x
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo "Cleanup"
|
||||||
|
ansible-playbook playbooks/docker_cleanup.yml
|
||||||
|
echo "Done"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup INT TERM EXIT
|
||||||
|
|
||||||
|
echo "Setup"
|
||||||
|
ANSIBLE_ROLES_PATH=.. ansible-playbook playbooks/docker_setup.yml
|
||||||
|
|
||||||
|
echo "Test docker_containers inventory 1"
|
||||||
|
ansible-playbook -i inventory_1.docker.yml playbooks/test_inventory_1.yml
|
||||||
|
|
||||||
|
echo "Test docker_containers inventory 2"
|
||||||
|
ansible-playbook -i inventory_2.docker.yml playbooks/test_inventory_2.yml
|
||||||
228
tests/unit/plugins/inventory/test_docker_containers.py
Normal file
228
tests/unit/plugins/inventory/test_docker_containers.py
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
# Copyright (c), Felix Fontein <felix@fontein.de>, 2020
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mock import MagicMock
|
||||||
|
|
||||||
|
from ansible import constants as C
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.inventory.data import InventoryData
|
||||||
|
from ansible.inventory.manager import InventoryManager
|
||||||
|
|
||||||
|
from ansible_collections.community.docker.plugins.inventory.docker_containers import InventoryModule
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def inventory():
|
||||||
|
r = InventoryModule()
|
||||||
|
r.inventory = InventoryData()
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
LOVING_THARP = {
|
||||||
|
'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a',
|
||||||
|
'Name': '/loving_tharp',
|
||||||
|
'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385',
|
||||||
|
'State': {
|
||||||
|
'Running': True,
|
||||||
|
},
|
||||||
|
'Config': {
|
||||||
|
'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LOVING_THARP_STACK = {
|
||||||
|
'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a',
|
||||||
|
'Name': '/loving_tharp',
|
||||||
|
'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385',
|
||||||
|
'State': {
|
||||||
|
'Running': True,
|
||||||
|
},
|
||||||
|
'Config': {
|
||||||
|
'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0',
|
||||||
|
'Labels': {
|
||||||
|
'com.docker.stack.namespace': 'my_stack',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'NetworkSettings': {
|
||||||
|
'Ports': {
|
||||||
|
'22/tcp': [
|
||||||
|
{
|
||||||
|
'HostIp': '0.0.0.0',
|
||||||
|
'HostPort': '32802'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LOVING_THARP_SERVICE = {
|
||||||
|
'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a',
|
||||||
|
'Name': '/loving_tharp',
|
||||||
|
'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385',
|
||||||
|
'State': {
|
||||||
|
'Running': True,
|
||||||
|
},
|
||||||
|
'Config': {
|
||||||
|
'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0',
|
||||||
|
'Labels': {
|
||||||
|
'com.docker.swarm.service.name': 'my_service',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_get_option(options, default=False):
|
||||||
|
def get_option(option):
|
||||||
|
if option in options:
|
||||||
|
return options[option]
|
||||||
|
return default
|
||||||
|
|
||||||
|
return get_option
|
||||||
|
|
||||||
|
|
||||||
|
class FakeClient(object):
|
||||||
|
def __init__(self, *hosts):
|
||||||
|
self.hosts = dict()
|
||||||
|
self.list_reply = []
|
||||||
|
for host in hosts:
|
||||||
|
self.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
|
||||||
|
|
||||||
|
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 test_populate(inventory, mocker):
|
||||||
|
client = FakeClient(LOVING_THARP)
|
||||||
|
|
||||||
|
inventory.get_option = mocker.MagicMock(side_effect=create_get_option({
|
||||||
|
'verbose_output': True,
|
||||||
|
'connection_type': 'docker-api',
|
||||||
|
'add_legacy_groups': False,
|
||||||
|
'compose': {},
|
||||||
|
'groups': {},
|
||||||
|
'keyed_groups': {},
|
||||||
|
}))
|
||||||
|
inventory._populate(client)
|
||||||
|
|
||||||
|
host_1 = inventory.inventory.get_host('loving_tharp')
|
||||||
|
host_1_vars = host_1.get_vars()
|
||||||
|
|
||||||
|
assert host_1_vars['ansible_host'] == 'loving_tharp'
|
||||||
|
assert host_1_vars['ansible_connection'] == 'community.docker.docker_api'
|
||||||
|
assert 'ansible_ssh_host' not in host_1_vars
|
||||||
|
assert 'ansible_ssh_port' not in host_1_vars
|
||||||
|
assert 'docker_state' in host_1_vars
|
||||||
|
assert 'docker_config' in host_1_vars
|
||||||
|
assert 'docker_image' in host_1_vars
|
||||||
|
|
||||||
|
assert len(inventory.inventory.groups['ungrouped'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups['all'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups) == 2
|
||||||
|
assert len(inventory.inventory.hosts) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_populate_service(inventory, mocker):
|
||||||
|
client = FakeClient(LOVING_THARP_SERVICE)
|
||||||
|
|
||||||
|
inventory.get_option = mocker.MagicMock(side_effect=create_get_option({
|
||||||
|
'verbose_output': False,
|
||||||
|
'connection_type': 'docker-cli',
|
||||||
|
'add_legacy_groups': True,
|
||||||
|
'compose': {},
|
||||||
|
'groups': {},
|
||||||
|
'keyed_groups': {},
|
||||||
|
'docker_host': 'unix://var/run/docker.sock',
|
||||||
|
}))
|
||||||
|
inventory._populate(client)
|
||||||
|
|
||||||
|
host_1 = inventory.inventory.get_host('loving_tharp')
|
||||||
|
host_1_vars = host_1.get_vars()
|
||||||
|
|
||||||
|
assert host_1_vars['ansible_host'] == 'loving_tharp'
|
||||||
|
assert host_1_vars['ansible_connection'] == 'community.docker.docker'
|
||||||
|
assert 'ansible_ssh_host' not in host_1_vars
|
||||||
|
assert 'ansible_ssh_port' not in host_1_vars
|
||||||
|
assert 'docker_state' not in host_1_vars
|
||||||
|
assert 'docker_config' not in host_1_vars
|
||||||
|
assert 'docker_image' not in host_1_vars
|
||||||
|
|
||||||
|
assert len(inventory.inventory.groups['ungrouped'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups['all'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups['7bd547963679e'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['image_quay.io/ansible/ubuntu1804-test-container:1.21.0'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['loving_tharp'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['running'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['stopped'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups['service_my_service'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['unix://var/run/docker.sock'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups) == 10
|
||||||
|
assert len(inventory.inventory.hosts) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_populate_stack(inventory, mocker):
|
||||||
|
client = FakeClient(LOVING_THARP_STACK)
|
||||||
|
|
||||||
|
inventory.get_option = mocker.MagicMock(side_effect=create_get_option({
|
||||||
|
'verbose_output': False,
|
||||||
|
'connection_type': 'ssh',
|
||||||
|
'add_legacy_groups': True,
|
||||||
|
'compose': {},
|
||||||
|
'groups': {},
|
||||||
|
'keyed_groups': {},
|
||||||
|
'docker_host': 'unix://var/run/docker.sock',
|
||||||
|
'default_ip': '127.0.0.1',
|
||||||
|
'private_ssh_port': 22,
|
||||||
|
}))
|
||||||
|
inventory._populate(client)
|
||||||
|
|
||||||
|
host_1 = inventory.inventory.get_host('loving_tharp')
|
||||||
|
host_1_vars = host_1.get_vars()
|
||||||
|
|
||||||
|
assert host_1_vars['ansible_ssh_host'] == '127.0.0.1'
|
||||||
|
assert host_1_vars['ansible_ssh_port'] == '32802'
|
||||||
|
assert 'ansible_host' not in host_1_vars
|
||||||
|
assert 'ansible_connection' not in host_1_vars
|
||||||
|
assert 'docker_state' not in host_1_vars
|
||||||
|
assert 'docker_config' not in host_1_vars
|
||||||
|
assert 'docker_image' not in host_1_vars
|
||||||
|
|
||||||
|
assert len(inventory.inventory.groups['ungrouped'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups['all'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups['7bd547963679e'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['image_quay.io/ansible/ubuntu1804-test-container:1.21.0'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['loving_tharp'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['running'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['stopped'].hosts) == 0
|
||||||
|
assert len(inventory.inventory.groups['stack_my_stack'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups['unix://var/run/docker.sock'].hosts) == 1
|
||||||
|
assert len(inventory.inventory.groups) == 10
|
||||||
|
assert len(inventory.inventory.hosts) == 1
|
||||||
Loading…
Reference in New Issue
Block a user