From: Lorin Hochstein Date: Wed, 8 Feb 2017 06:20:09 +0000 (-0800) Subject: Include docker inventory plugin X-Git-Url: https://git.halfball.org/?a=commitdiff_plain;h=640606e6fc6a9b1f2efcfbefac9e2a2257430304;p=ansiblebook.git Include docker inventory plugin --- diff --git a/ch13/deploy.yml b/ch13/deploy.yml index 65c4852..c994330 100644 --- a/ch13/deploy.yml +++ b/ch13/deploy.yml @@ -1,5 +1,5 @@ - name: install Docker - hosts: all + hosts: dockerhosts become: True tasks: - name: install packages diff --git a/ch13/inventory/docker.py b/ch13/inventory/docker.py new file mode 100755 index 0000000..6cbfb5d --- /dev/null +++ b/ch13/inventory/docker.py @@ -0,0 +1,873 @@ +#!/usr/bin/env python +# +# (c) 2016 Paul Durivage +# Chris Houseknecht +# James Tanner +# +# This file is part of Ansible. +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +DOCUMENTATION = ''' + +Docker Inventory Script +======================= +The inventory script generates dynamic inventory by making API requests to one or more Docker APIs. It's dynamic +because the inventory is generated at run-time rather than being read from a static file. The script generates the +inventory by connecting to one or many Docker APIs and inspecting the containers it finds at each API. Which APIs the +script contacts can be defined using environment variables or a configuration file. + +Requirements +------------ + +Using the docker modules requires having docker-py +installed on the host running Ansible. To install docker-py: + + pip install docker-py + + +Run for Specific Host +--------------------- +When run for a specific container using the --host option this script returns the following hostvars: + +{ + "ansible_ssh_host": "", + "ansible_ssh_port": 0, + "docker_apparmorprofile": "", + "docker_args": [], + "docker_config": { + "AttachStderr": false, + "AttachStdin": false, + "AttachStdout": false, + "Cmd": [ + "/hello" + ], + "Domainname": "", + "Entrypoint": null, + "Env": null, + "Hostname": "9f2f80b0a702", + "Image": "hello-world", + "Labels": {}, + "OnBuild": null, + "OpenStdin": false, + "StdinOnce": false, + "Tty": false, + "User": "", + "Volumes": null, + "WorkingDir": "" + }, + "docker_created": "2016-04-18T02:05:59.659599249Z", + "docker_driver": "aufs", + "docker_execdriver": "native-0.2", + "docker_execids": null, + "docker_graphdriver": { + "Data": null, + "Name": "aufs" + }, + "docker_hostconfig": { + "Binds": null, + "BlkioWeight": 0, + "CapAdd": null, + "CapDrop": null, + "CgroupParent": "", + "ConsoleSize": [ + 0, + 0 + ], + "ContainerIDFile": "", + "CpuPeriod": 0, + "CpuQuota": 0, + "CpuShares": 0, + "CpusetCpus": "", + "CpusetMems": "", + "Devices": null, + "Dns": null, + "DnsOptions": null, + "DnsSearch": null, + "ExtraHosts": null, + "GroupAdd": null, + "IpcMode": "", + "KernelMemory": 0, + "Links": null, + "LogConfig": { + "Config": {}, + "Type": "json-file" + }, + "LxcConf": null, + "Memory": 0, + "MemoryReservation": 0, + "MemorySwap": 0, + "MemorySwappiness": null, + "NetworkMode": "default", + "OomKillDisable": false, + "PidMode": "host", + "PortBindings": null, + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "RestartPolicy": { + "MaximumRetryCount": 0, + "Name": "" + }, + "SecurityOpt": [ + "label:disable" + ], + "UTSMode": "", + "Ulimits": null, + "VolumeDriver": "", + "VolumesFrom": null + }, + "docker_hostnamepath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/hostname", + "docker_hostspath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/hosts", + "docker_id": "9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14", + "docker_image": "0a6ba66e537a53a5ea94f7c6a99c534c6adb12e3ed09326d4bf3b38f7c3ba4e7", + "docker_logpath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14-json.log", + "docker_mountlabel": "", + "docker_mounts": [], + "docker_name": "/hello-world", + "docker_networksettings": { + "Bridge": "", + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "HairpinMode": false, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "MacAddress": "", + "Networks": { + "bridge": { + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "" + } + }, + "Ports": null, + "SandboxID": "", + "SandboxKey": "", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null + }, + "docker_path": "/hello", + "docker_processlabel": "", + "docker_resolvconfpath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/resolv.conf", + "docker_restartcount": 0, + "docker_short_id": "9f2f80b0a7023", + "docker_state": { + "Dead": false, + "Error": "", + "ExitCode": 0, + "FinishedAt": "2016-04-18T02:06:00.296619369Z", + "OOMKilled": false, + "Paused": false, + "Pid": 0, + "Restarting": false, + "Running": false, + "StartedAt": "2016-04-18T02:06:00.272065041Z", + "Status": "exited" + } +} + +Groups +------ +When run in --list mode (the default), container instances are grouped by: + + - container id + - container name + - container short id + - image_name (image_) + - docker_host + - running + - stopped + + +Configuration: +-------------- +You can control the behavior of the inventory script by passing arguments, defining environment variables, or +creating a configuration file named docker.yml (sample provided in ansible/contrib/inventory). The order of precedence +is command line args, then the docker.yml file and finally environment variables. + +Environment variables: +...................... + +To connect to a single Docker API the following variables can be defined in the environment to control the connection +options. These are the same environment variables used by the Docker modules. + + DOCKER_HOST + The URL or Unix socket path used to connect to the Docker API. Defaults to unix://var/run/docker.sock. + + DOCKER_API_VERSION: + The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported + by docker-py. + + DOCKER_TIMEOUT: + The maximum amount of time in seconds to wait on a response fromm the API. Defaults to 60 seconds. + + DOCKER_TLS: + Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server. + Defaults to False. + + DOCKER_TLS_VERIFY: + Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server. + Default is False + + DOCKER_TLS_HOSTNAME: + When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults + to localhost. + + DOCKER_CERT_PATH: + Path to the directory containing the client certificate, client key and CA certificate. + + DOCKER_SSL_VERSION: + Provide a valid SSL version number. Default value determined by docker-py, which at the time of this writing + was 1.0 + +In addition to the connection variables there are a couple variables used to control the execution and output of the +script: + + DOCKER_CONFIG_FILE + Path to the configuration file. Defaults to ./docker.yml. + + DOCKER_PRIVATE_SSH_PORT: + The private port (container port) on which SSH is listening for connections. Defaults to 22. + + DOCKER_DEFAULT_IP: + The IP address to assign to ansible_host when the container's SSH port is mapped to interface '0.0.0.0'. + + +Configuration File +.................. + +Using a configuration file provides a means for defining a set of Docker APIs from which to build an inventory. + +The default name of the file is derived from the name of the inventory script. By default the script will look for +basename of the script (i.e. docker) with an extension of '.yml'. + +You can also override the default name of the script by defining DOCKER_CONFIG_FILE in the environment. + +Here's what you can define in docker_inventory.yml: + + defaults + Defines a default connection. Defaults will be taken from this and applied to any values not provided + for a host defined in the hosts list. + + hosts + If you wish to get inventory from more than one Docker host, define a hosts list. + +For the default host and each host in the hosts list define the following attributes: + + host: + description: The URL or Unix socket path used to connect to the Docker API. + required: yes + + tls: + description: Connect using TLS without verifying the authenticity of the Docker host server. + default: false + required: false + + tls_verify: + description: Connect using TLS without verifying the authenticity of the Docker host server. + default: false + required: false + + cert_path: + description: Path to the client's TLS certificate file. + default: null + required: false + + cacert_path: + description: Use a CA certificate when performing server verification by providing the path to a CA certificate file. + default: null + required: false + + key_path: + description: Path to the client's TLS key file. + default: null + required: false + + version: + description: The Docker API version. + required: false + default: will be supplied by the docker-py module. + + timeout: + description: The amount of time in seconds to wait on an API response. + required: false + default: 60 + + 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'. + required: false + default: 127.0.0.1 + + private_ssh_port: + description: The port containers use for SSH + required: false + default: 22 + +Examples +-------- + +# Connect to the Docker API on localhost port 4243 and format the JSON output +DOCKER_HOST=tcp://localhost:4243 ./docker.py --pretty + +# Any container's ssh port exposed on 0.0.0.0 will be mapped to +# another IP address (where Ansible will attempt to connect via SSH) +DOCKER_DEFAULT_IP=1.2.3.4 ./docker.py --pretty + +# Run as input to a playbook: +ansible-playbook -i ~/projects/ansible/contrib/inventory/docker.py docker_inventory_test.yml + +# Simple playbook to invoke with the above example: + + - name: Test docker_inventory + hosts: all + connection: local + gather_facts: no + tasks: + - debug: msg="Container - {{ inventory_hostname }}" + +''' + +import os +import sys +import json +import argparse +import re +import yaml + +from collections import defaultdict +# Manipulation of the path is needed because the docker-py +# module is imported by the name docker, and because this file +# is also named docker +for path in [os.getcwd(), '', os.path.dirname(os.path.abspath(__file__))]: + try: + del sys.path[sys.path.index(path)] + except: + pass + +HAS_DOCKER_PY = True +HAS_DOCKER_ERROR = False + +try: + from docker import Client + from docker.errors import APIError, TLSParameterError + from docker.tls import TLSConfig + from docker.constants import DEFAULT_TIMEOUT_SECONDS, DEFAULT_DOCKER_API_VERSION +except ImportError as exc: + HAS_DOCKER_ERROR = str(exc) + HAS_DOCKER_PY = False + +DEFAULT_DOCKER_HOST = 'unix://var/run/docker.sock' +DEFAULT_TLS = False +DEFAULT_TLS_VERIFY = False +DEFAULT_IP = '127.0.0.1' +DEFAULT_SSH_PORT = '22' + +BOOLEANS_TRUE = ['yes', 'on', '1', 'true', 1, True] +BOOLEANS_FALSE = ['no', 'off', '0', 'false', 0, False] + + +DOCKER_ENV_ARGS = dict( + config_file='DOCKER_CONFIG_FILE', + docker_host='DOCKER_HOST', + api_version='DOCKER_API_VERSION', + cert_path='DOCKER_CERT_PATH', + ssl_version='DOCKER_SSL_VERSION', + tls='DOCKER_TLS', + tls_verify='DOCKER_TLS_VERIFY', + timeout='DOCKER_TIMEOUT', + private_ssh_port='DOCKER_DEFAULT_SSH_PORT', + default_ip='DOCKER_DEFAULT_IP', +) + + +def fail(msg): + sys.stderr.write("%s\n" % msg) + sys.exit(1) + + +def log(msg, pretty_print=False): + if pretty_print: + print(json.dumps(msg, sort_keys=True, indent=2)) + else: + print(msg + u'\n') + + +class AnsibleDockerClient(Client): + def __init__(self, auth_params, debug): + + self.auth_params = auth_params + self.debug = debug + self._connect_params = self._get_connect_params() + + try: + super(AnsibleDockerClient, self).__init__(**self._connect_params) + except APIError as exc: + self.fail("Docker API error: %s" % exc) + except Exception as exc: + self.fail("Error connecting: %s" % exc) + + def fail(self, msg): + fail(msg) + + def log(self, msg, pretty_print=False): + if self.debug: + log(msg, pretty_print) + + def _get_tls_config(self, **kwargs): + self.log("get_tls_config:") + for key in kwargs: + self.log(" %s: %s" % (key, kwargs[key])) + try: + tls_config = TLSConfig(**kwargs) + return tls_config + except TLSParameterError as exc: + self.fail("TLS config error: %s" % exc) + + def _get_connect_params(self): + auth = self.auth_params + + self.log("auth params:") + for key in auth: + self.log(" %s: %s" % (key, auth[key])) + + if auth['tls'] or auth['tls_verify']: + auth['docker_host'] = auth['docker_host'].replace('tcp://', 'https://') + + if auth['tls'] and auth['cert_path'] and auth['key_path']: + # TLS with certs and no host verification + tls_config = self._get_tls_config(client_cert=(auth['cert_path'], auth['key_path']), + verify=False, + ssl_version=auth['ssl_version']) + return dict(base_url=auth['docker_host'], + tls=tls_config, + version=auth['api_version'], + timeout=auth['timeout']) + + if auth['tls']: + # TLS with no certs and not host verification + tls_config = self._get_tls_config(verify=False, + ssl_version=auth['ssl_version']) + return dict(base_url=auth['docker_host'], + tls=tls_config, + version=auth['api_version'], + timeout=auth['timeout']) + + if auth['tls_verify'] and auth['cert_path'] and auth['key_path']: + # TLS with certs and host verification + if auth['cacert_path']: + tls_config = self._get_tls_config(client_cert=(auth['cert_path'], auth['key_path']), + ca_cert=auth['cacert_path'], + verify=True, + assert_hostname=auth['tls_hostname'], + ssl_version=auth['ssl_version']) + else: + tls_config = self._get_tls_config(client_cert=(auth['cert_path'], auth['key_path']), + verify=True, + assert_hostname=auth['tls_hostname'], + ssl_version=auth['ssl_version']) + + return dict(base_url=auth['docker_host'], + tls=tls_config, + version=auth['api_version'], + timeout=auth['timeout']) + + if auth['tls_verify'] and auth['cacert_path']: + # TLS with cacert only + tls_config = self._get_tls_config(ca_cert=auth['cacert_path'], + assert_hostname=auth['tls_hostname'], + verify=True, + ssl_version=auth['ssl_version']) + return dict(base_url=auth['docker_host'], + tls=tls_config, + version=auth['api_version'], + timeout=auth['timeout']) + + if auth['tls_verify']: + # TLS with verify and no certs + tls_config = self._get_tls_config(verify=True, + assert_hostname=auth['tls_hostname'], + ssl_version=auth['ssl_version']) + return dict(base_url=auth['docker_host'], + tls=tls_config, + version=auth['api_version'], + timeout=auth['timeout']) + # No TLS + return dict(base_url=auth['docker_host'], + version=auth['api_version'], + timeout=auth['timeout']) + + def _handle_ssl_error(self, error): + match = re.match(r"hostname.*doesn\'t match (\'.*\')", str(error)) + if match: + msg = "You asked for verification that Docker host name matches %s. The actual hostname is %s. " \ + "Most likely you need to set DOCKER_TLS_HOSTNAME or pass tls_hostname with a value of %s. " \ + "You may also use TLS without verification by setting the tls parameter to true." \ + % (self.auth_params['tls_hostname'], match.group(1)) + self.fail(msg) + self.fail("SSL Exception: %s" % (error)) + + +class EnvArgs(object): + def __init__(self): + self.config_file = None + self.docker_host = None + self.api_version = None + self.cert_path = None + self.ssl_version = None + self.tls = None + self.tls_verify = None + self.tls_hostname = None + self.timeout = None + self.default_ssh_port = None + self.default_ip = None + + +class DockerInventory(object): + + def __init__(self): + self._args = self._parse_cli_args() + self._env_args = self._parse_env_args() + self.groups = defaultdict(list) + self.hostvars = defaultdict(dict) + + def run(self): + config_from_file = self._parse_config_file() + if not config_from_file: + config_from_file = dict() + docker_hosts = self.get_hosts(config_from_file) + + for host in docker_hosts: + client = AnsibleDockerClient(host, self._args.debug) + self.get_inventory(client, host) + + if not self._args.host: + self.groups['docker_hosts'] = [host.get('docker_host') for host in docker_hosts] + self.groups['_meta'] = dict( + hostvars=self.hostvars + ) + print(self._json_format_dict(self.groups, pretty_print=self._args.pretty)) + else: + print(self._json_format_dict(self.hostvars.get(self._args.host, dict()), pretty_print=self._args.pretty)) + + sys.exit(0) + + def get_inventory(self, client, host): + + ssh_port = host.get('default_ssh_port') + default_ip = host.get('default_ip') + hostname = host.get('docker_host') + + try: + containers = client.containers(all=True) + except Exception as exc: + self.fail("Error fetching containers for host %s - %s" % (hostname, str(exc))) + + for container in containers: + id = container.get('Id') + short_id = id[:13] + + try: + name = container.get('Names', list()).pop(0).lstrip('/') + except IndexError: + name = short_id + + if not self._args.host or (self._args.host and self._args.host in [name, id, short_id]): + try: + inspect = client.inspect_container(id) + except Exception as exc: + self.fail("Error inspecting container %s - %s" % (name, str(exc))) + + running = inspect.get('State', dict()).get('Running') + + # Add container to groups + image_name = inspect.get('Config', dict()).get('Image') + if image_name: + self.groups["image_%s" % (image_name)].append(name) + + self.groups[id].append(name) + self.groups[name].append(name) + if short_id not in self.groups.keys(): + self.groups[short_id].append(name) + self.groups[hostname].append(name) + + if running is True: + self.groups['running'].append(name) + else: + self.groups['stopped'].append(name) + + # Figure ous 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 = dict( + ansible_ssh_host=ip, + ansible_ssh_port=port.get('HostPort', int()), + docker_name=name, + docker_short_id=short_id + ) + + for key in inspect: + fact_key = self._slugify(key) + facts[fact_key] = inspect.get(key) + + self.hostvars[name].update(facts) + + def _slugify(self, value): + return 'docker_%s' % (re.sub('[^\w-]', '_', value).lower().lstrip('_')) + + def get_hosts(self, config): + ''' + Determine the list of docker hosts we need to talk to. + + :param config: dictionary read from config file. can be empty. + :return: list of connection dictionaries + ''' + hosts = list() + + hosts_list = config.get('hosts') + defaults = config.get('defaults', dict()) + self.log('defaults:') + self.log(defaults, pretty_print=True) + def_host = defaults.get('host') + def_tls = defaults.get('tls') + def_tls_verify = defaults.get('tls_verify') + def_tls_hostname = defaults.get('tls_hostname') + def_ssl_version = defaults.get('ssl_version') + def_cert_path = defaults.get('cert_path') + def_cacert_path = defaults.get('cacert_path') + def_key_path = defaults.get('key_path') + def_version = defaults.get('version') + def_timeout = defaults.get('timeout') + def_ip = defaults.get('default_ip') + def_ssh_port = defaults.get('private_ssh_port') + + if hosts_list: + # use hosts from config file + for host in hosts_list: + docker_host = host.get('host') or def_host or self._args.docker_host or \ + self._env_args.docker_host or DEFAULT_DOCKER_HOST + api_version = host.get('version') or def_version or self._args.api_version or \ + self._env_args.api_version or DEFAULT_DOCKER_API_VERSION + tls_hostname = host.get('tls_hostname') or def_tls_hostname or self._args.tls_hostname or \ + self._env_args.tls_hostname + tls_verify = host.get('tls_verify') or def_tls_verify or self._args.tls_verify or \ + self._env_args.tls_verify or DEFAULT_TLS_VERIFY + tls = host.get('tls') or def_tls or self._args.tls or self._env_args.tls or DEFAULT_TLS + ssl_version = host.get('ssl_version') or def_ssl_version or self._args.ssl_version or \ + self._env_args.ssl_version + + cert_path = host.get('cert_path') or def_cert_path or self._args.cert_path or \ + self._env_args.cert_path + if cert_path and cert_path == self._env_args.cert_path: + cert_path = os.path.join(cert_path, 'cert.pem') + + cacert_path = host.get('cacert_path') or def_cacert_path or self._args.cacert_path or \ + self._env_args.cert_path + if cacert_path and cacert_path == self._env_args.cert_path: + cacert_path = os.path.join(cacert_path, 'ca.pem') + + key_path = host.get('key_path') or def_key_path or self._args.key_path or \ + self._env_args.cert_path + if key_path and key_path == self._env_args.cert_path: + key_path = os.path.join(key_path, 'key.pem') + + timeout = host.get('timeout') or def_timeout or self._args.timeout or self._env_args.timeout or \ + DEFAULT_TIMEOUT_SECONDS + default_ip = host.get('default_ip') or def_ip or self._args.default_ip_address or \ + DEFAULT_IP + default_ssh_port = host.get('private_ssh_port') or def_ssh_port or self._args.private_ssh_port or \ + DEFAULT_SSH_PORT + host_dict = dict( + docker_host=docker_host, + api_version=api_version, + tls=tls, + tls_verify=tls_verify, + tls_hostname=tls_hostname, + cert_path=cert_path, + cacert_path=cacert_path, + key_path=key_path, + ssl_version=ssl_version, + timeout=timeout, + default_ip=default_ip, + default_ssh_port=default_ssh_port, + ) + hosts.append(host_dict) + else: + # use default definition + docker_host = def_host or self._args.docker_host or self._env_args.docker_host or DEFAULT_DOCKER_HOST + api_version = def_version or self._args.api_version or self._env_args.api_version or \ + DEFAULT_DOCKER_API_VERSION + tls_hostname = def_tls_hostname or self._args.tls_hostname or self._env_args.tls_hostname + tls_verify = def_tls_verify or self._args.tls_verify or self._env_args.tls_verify or DEFAULT_TLS_VERIFY + tls = def_tls or self._args.tls or self._env_args.tls or DEFAULT_TLS + ssl_version = def_ssl_version or self._args.ssl_version or self._env_args.ssl_version + + cert_path = def_cert_path or self._args.cert_path or self._env_args.cert_path + if cert_path and cert_path == self._env_args.cert_path: + cert_path = os.path.join(cert_path, 'cert.pem') + + cacert_path = def_cacert_path or self._args.cacert_path or self._env_args.cert_path + if cacert_path and cacert_path == self._env_args.cert_path: + cacert_path = os.path.join(cacert_path, 'ca.pem') + + key_path = def_key_path or self._args.key_path or self._env_args.cert_path + if key_path and key_path == self._env_args.cert_path: + key_path = os.path.join(key_path, 'key.pem') + + timeout = def_timeout or self._args.timeout or self._env_args.timeout or DEFAULT_TIMEOUT_SECONDS + default_ip = def_ip or self._args.default_ip_address or DEFAULT_IP + default_ssh_port = def_ssh_port or self._args.private_ssh_port or DEFAULT_SSH_PORT + host_dict = dict( + docker_host=docker_host, + api_version=api_version, + tls=tls, + tls_verify=tls_verify, + tls_hostname=tls_hostname, + cert_path=cert_path, + cacert_path=cacert_path, + key_path=key_path, + ssl_version=ssl_version, + timeout=timeout, + default_ip=default_ip, + default_ssh_port=default_ssh_port, + ) + hosts.append(host_dict) + self.log("hosts: ") + self.log(hosts, pretty_print=True) + return hosts + + def _parse_config_file(self): + config = dict() + config_path = None + + if self._args.config_file: + config_path = self._args.config_file + elif self._env_args.config_file: + config_path = self._env_args.config_file + + if config_path: + try: + config_file = os.path.abspath(config_path) + except: + config_file = None + + if config_file and os.path.exists(config_file): + with open(config_file) as f: + try: + config = yaml.safe_load(f.read()) + except Exception as exc: + self.fail("Error: parsing %s - %s" % (config_path, str(exc))) + return config + + def log(self, msg, pretty_print=False): + if self._args.debug: + log(msg, pretty_print) + + def fail(self, msg): + fail(msg) + + def _parse_env_args(self): + args = EnvArgs() + for key, value in DOCKER_ENV_ARGS.items(): + if os.environ.get(value): + val = os.environ.get(value) + if val in BOOLEANS_TRUE: + val = True + if val in BOOLEANS_FALSE: + val = False + setattr(args, key, val) + return args + + def _parse_cli_args(self): + # Parse command line arguments + + basename = os.path.splitext(os.path.basename(__file__))[0] + default_config = basename + '.yml' + + parser = argparse.ArgumentParser( + description='Return Ansible inventory for one or more Docker hosts.') + parser.add_argument('--list', action='store_true', default=True, + help='List all containers (default: True)') + parser.add_argument('--debug', action='store_true', default=False, + help='Send debug messages to STDOUT') + parser.add_argument('--host', action='store', + help='Only get information for a specific container.') + parser.add_argument('--pretty', action='store_true', default=False, + help='Pretty print JSON output(default: False)') + parser.add_argument('--config-file', action='store', default=default_config, + help="Name of the config file to use. Default is %s" % (default_config)) + parser.add_argument('--docker-host', action='store', default=None, + help="The base url or Unix sock path to connect to the docker daemon. Defaults to %s" + % (DEFAULT_DOCKER_HOST)) + parser.add_argument('--tls-hostname', action='store', default='localhost', + help="Host name to expect in TLS certs. Defaults to 'localhost'") + parser.add_argument('--api-version', action='store', default=None, + help="Docker daemon API version. Defaults to %s" % (DEFAULT_DOCKER_API_VERSION)) + parser.add_argument('--timeout', action='store', default=None, + help="Docker connection timeout in seconds. Defaults to %s" + % (DEFAULT_TIMEOUT_SECONDS)) + parser.add_argument('--cacert-path', action='store', default=None, + help="Path to the TLS certificate authority pem file.") + parser.add_argument('--cert-path', action='store', default=None, + help="Path to the TLS certificate pem file.") + parser.add_argument('--key-path', action='store', default=None, + help="Path to the TLS encryption key pem file.") + parser.add_argument('--ssl-version', action='store', default=None, + help="TLS version number") + parser.add_argument('--tls', action='store_true', default=None, + help="Use TLS. Defaults to %s" % (DEFAULT_TLS)) + parser.add_argument('--tls-verify', action='store_true', default=None, + help="Verify TLS certificates. Defaults to %s" % (DEFAULT_TLS_VERIFY)) + parser.add_argument('--private-ssh-port', action='store', default=None, + help="Default private container SSH Port. Defaults to %s" % (DEFAULT_SSH_PORT)) + parser.add_argument('--default-ip-address', action='store', default=None, + help="Default container SSH IP address. Defaults to %s" % (DEFAULT_IP)) + return parser.parse_args() + + def _json_format_dict(self, data, pretty_print=False): + # format inventory data for output + if pretty_print: + return json.dumps(data, sort_keys=True, indent=4) + else: + return json.dumps(data) + + +def main(): + + if not HAS_DOCKER_PY: + fail("Failed to import docker-py. Try `pip install docker-py` - %s" % (HAS_DOCKER_ERROR)) + + DockerInventory().run() + +main() diff --git a/ch13/inventory b/ch13/inventory/hosts similarity index 84% rename from ch13/inventory rename to ch13/inventory/hosts index f791423..9e68559 100644 --- a/ch13/inventory +++ b/ch13/inventory/hosts @@ -1,9 +1,13 @@ -[all:vars] +[dockerhosts:vars] ansible_ssh_user=vagrant database_name=ghost database_user=ghost database_password=mysupersecretpassword +[dockerhosts:children] +postgres +ghost + [postgres] 192.168.33.9 ansible_ssh_private_key_file=.vagrant/machines/postgres/virtualbox/private_key