d0c6d2ff1c
renamed plugins to contrib (they are not really plugins) rewrote README.md to reflect new usage added new dir to setup.py so it gets copied with installation, in views of making using inventory scripts easier in teh future
359 lines
12 KiB
Python
Executable file
359 lines
12 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
# (c) 2013, Paul Durivage <paul.durivage@gmail.com>
|
|
#
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
#
|
|
# Author: Paul Durivage <paul.durivage@gmail.com>
|
|
#
|
|
# Description:
|
|
# This module queries local or remote Docker daemons and generates
|
|
# inventory information.
|
|
#
|
|
# This plugin does not support targeting of specific hosts using the --host
|
|
# flag. Instead, it queries the Docker API for each container, running
|
|
# or not, and returns this data all once.
|
|
#
|
|
# The plugin returns the following custom attributes on Docker containers:
|
|
# docker_args
|
|
# docker_config
|
|
# docker_created
|
|
# docker_driver
|
|
# docker_exec_driver
|
|
# docker_host_config
|
|
# docker_hostname_path
|
|
# docker_hosts_path
|
|
# docker_id
|
|
# docker_image
|
|
# docker_name
|
|
# docker_network_settings
|
|
# docker_path
|
|
# docker_resolv_conf_path
|
|
# docker_state
|
|
# docker_volumes
|
|
# docker_volumes_rw
|
|
#
|
|
# Requirements:
|
|
# The docker-py module: https://github.com/dotcloud/docker-py
|
|
#
|
|
# Notes:
|
|
# A config file can be used to configure this inventory module, and there
|
|
# are several environment variables that can be set to modify the behavior
|
|
# of the plugin at runtime:
|
|
# DOCKER_CONFIG_FILE
|
|
# DOCKER_HOST
|
|
# DOCKER_VERSION
|
|
# DOCKER_TIMEOUT
|
|
# DOCKER_PRIVATE_SSH_PORT
|
|
# DOCKER_DEFAULT_IP
|
|
#
|
|
# Environment Variables:
|
|
# environment variable: DOCKER_CONFIG_FILE
|
|
# description:
|
|
# - A path to a Docker inventory hosts/defaults file in YAML format
|
|
# - A sample file has been provided, colocated with the inventory
|
|
# file called 'docker.yml'
|
|
# required: false
|
|
# default: Uses docker.docker.Client constructor defaults
|
|
# environment variable: DOCKER_HOST
|
|
# description:
|
|
# - The socket on which to connect to a Docker daemon API
|
|
# required: false
|
|
# default: Uses docker.docker.Client constructor defaults
|
|
# environment variable: DOCKER_VERSION
|
|
# description:
|
|
# - Version of the Docker API to use
|
|
# default: Uses docker.docker.Client constructor defaults
|
|
# required: false
|
|
# environment variable: DOCKER_TIMEOUT
|
|
# description:
|
|
# - Timeout in seconds for connections to Docker daemon API
|
|
# default: Uses docker.docker.Client constructor defaults
|
|
# required: false
|
|
# environment variable: DOCKER_PRIVATE_SSH_PORT
|
|
# description:
|
|
# - The private port (container port) on which SSH is listening
|
|
# for connections
|
|
# default: 22
|
|
# required: false
|
|
# environment variable: DOCKER_DEFAULT_IP
|
|
# description:
|
|
# - This environment variable overrides the container SSH connection
|
|
# IP address (aka, 'ansible_ssh_host')
|
|
#
|
|
# This option allows one to override the ansible_ssh_host whenever
|
|
# Docker has exercised its default behavior of binding private ports
|
|
# to all interfaces of the Docker host. This behavior, when dealing
|
|
# with remote Docker hosts, does not allow Ansible to determine
|
|
# a proper host IP address on which to connect via SSH to containers.
|
|
# By default, this inventory module assumes all 0.0.0.0-exposed
|
|
# ports to be bound to localhost:<port>. To override this
|
|
# behavior, for example, to bind a container's SSH port to the public
|
|
# interface of its host, one must manually set this IP.
|
|
#
|
|
# It is preferable to begin to launch Docker containers with
|
|
# ports exposed on publicly accessible IP addresses, particularly
|
|
# if the containers are to be targeted by Ansible for remote
|
|
# configuration, not accessible via localhost SSH connections.
|
|
#
|
|
# Docker containers can be explicitly exposed on IP addresses by
|
|
# a) starting the daemon with the --ip argument
|
|
# b) running containers with the -P/--publish ip::containerPort
|
|
# argument
|
|
# default: 127.0.0.1 if port exposed on 0.0.0.0 by Docker
|
|
# required: false
|
|
#
|
|
# Examples:
|
|
# Use the config file:
|
|
# DOCKER_CONFIG_FILE=./docker.yml docker.py --list
|
|
#
|
|
# Connect to docker instance on localhost port 4243
|
|
# DOCKER_HOST=tcp://localhost:4243 docker.py --list
|
|
#
|
|
# Any container's ssh port exposed on 0.0.0.0 will mapped to
|
|
# another IP address (where Ansible will attempt to connect via SSH)
|
|
# DOCKER_DEFAULT_IP=1.2.3.4 docker.py --list
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
|
|
from UserDict import UserDict
|
|
from collections import defaultdict
|
|
|
|
import yaml
|
|
|
|
from requests import HTTPError, ConnectionError
|
|
|
|
# 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
|
|
|
|
try:
|
|
import docker
|
|
except ImportError:
|
|
print('docker-py is required for this module')
|
|
sys.exit(1)
|
|
|
|
|
|
class HostDict(UserDict):
|
|
def __setitem__(self, key, value):
|
|
if value is not None:
|
|
self.data[key] = value
|
|
|
|
def update(self, dict=None, **kwargs):
|
|
if dict is None:
|
|
pass
|
|
elif isinstance(dict, UserDict):
|
|
for k, v in dict.data.items():
|
|
self[k] = v
|
|
else:
|
|
for k, v in dict.items():
|
|
self[k] = v
|
|
if len(kwargs):
|
|
for k, v in kwargs.items():
|
|
self[k] = v
|
|
|
|
|
|
def write_stderr(string):
|
|
sys.stderr.write('%s\n' % string)
|
|
|
|
|
|
def setup():
|
|
config = dict()
|
|
config_file = os.environ.get('DOCKER_CONFIG_FILE')
|
|
if config_file:
|
|
try:
|
|
config_file = os.path.abspath(config_file)
|
|
except Exception as e:
|
|
write_stderr(e)
|
|
sys.exit(1)
|
|
|
|
with open(config_file) as f:
|
|
try:
|
|
config = yaml.safe_load(f.read())
|
|
except Exception as e:
|
|
write_stderr(e)
|
|
sys.exit(1)
|
|
|
|
# Environment Variables
|
|
env_base_url = os.environ.get('DOCKER_HOST')
|
|
env_version = os.environ.get('DOCKER_VERSION')
|
|
env_timeout = os.environ.get('DOCKER_TIMEOUT')
|
|
env_ssh_port = os.environ.get('DOCKER_PRIVATE_SSH_PORT', '22')
|
|
env_default_ip = os.environ.get('DOCKER_DEFAULT_IP', '127.0.0.1')
|
|
# Config file defaults
|
|
defaults = config.get('defaults', dict())
|
|
def_host = defaults.get('host')
|
|
def_version = defaults.get('version')
|
|
def_timeout = defaults.get('timeout')
|
|
def_default_ip = defaults.get('default_ip')
|
|
def_ssh_port = defaults.get('private_ssh_port')
|
|
|
|
hosts = list()
|
|
|
|
if config:
|
|
hosts_list = config.get('hosts', list())
|
|
# Look to the config file's defined hosts
|
|
if hosts_list:
|
|
for host in hosts_list:
|
|
baseurl = host.get('host') or def_host or env_base_url
|
|
version = host.get('version') or def_version or env_version
|
|
timeout = host.get('timeout') or def_timeout or env_timeout
|
|
default_ip = host.get('default_ip') or def_default_ip or env_default_ip
|
|
ssh_port = host.get('private_ssh_port') or def_ssh_port or env_ssh_port
|
|
|
|
hostdict = HostDict(
|
|
base_url=baseurl,
|
|
version=version,
|
|
timeout=timeout,
|
|
default_ip=default_ip,
|
|
private_ssh_port=ssh_port,
|
|
)
|
|
hosts.append(hostdict)
|
|
# Look to the defaults
|
|
else:
|
|
hostdict = HostDict(
|
|
base_url=def_host,
|
|
version=def_version,
|
|
timeout=def_timeout,
|
|
default_ip=def_default_ip,
|
|
private_ssh_port=def_ssh_port,
|
|
)
|
|
hosts.append(hostdict)
|
|
# Look to the environment
|
|
else:
|
|
hostdict = HostDict(
|
|
base_url=env_base_url,
|
|
version=env_version,
|
|
timeout=env_timeout,
|
|
default_ip=env_default_ip,
|
|
private_ssh_port=env_ssh_port,
|
|
)
|
|
hosts.append(hostdict)
|
|
|
|
return hosts
|
|
|
|
|
|
def list_groups():
|
|
hosts = setup()
|
|
groups = defaultdict(list)
|
|
hostvars = defaultdict(dict)
|
|
|
|
for host in hosts:
|
|
ssh_port = host.pop('private_ssh_port', None)
|
|
default_ip = host.pop('default_ip', None)
|
|
hostname = host.get('base_url')
|
|
|
|
try:
|
|
client = docker.Client(**host)
|
|
containers = client.containers(all=True)
|
|
except (HTTPError, ConnectionError) as e:
|
|
write_stderr(e)
|
|
sys.exit(1)
|
|
|
|
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 id:
|
|
continue
|
|
|
|
inspect = client.inspect_container(id)
|
|
running = inspect.get('State', dict()).get('Running')
|
|
|
|
groups[id].append(name)
|
|
groups[name].append(name)
|
|
if not short_id in groups.keys():
|
|
groups[short_id].append(name)
|
|
groups[hostname].append(name)
|
|
|
|
if running is True:
|
|
groups['running'].append(name)
|
|
else:
|
|
groups['stopped'].append(name)
|
|
|
|
try:
|
|
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 = ''
|
|
|
|
container_info = dict(
|
|
ansible_ssh_host=ip,
|
|
ansible_ssh_port=port.get('HostPort', int()),
|
|
docker_args=inspect.get('Args'),
|
|
docker_config=inspect.get('Config'),
|
|
docker_created=inspect.get('Created'),
|
|
docker_driver=inspect.get('Driver'),
|
|
docker_exec_driver=inspect.get('ExecDriver'),
|
|
docker_host_config=inspect.get('HostConfig'),
|
|
docker_hostname_path=inspect.get('HostnamePath'),
|
|
docker_hosts_path=inspect.get('HostsPath'),
|
|
docker_id=inspect.get('ID'),
|
|
docker_image=inspect.get('Image'),
|
|
docker_name=name,
|
|
docker_network_settings=inspect.get('NetworkSettings'),
|
|
docker_path=inspect.get('Path'),
|
|
docker_resolv_conf_path=inspect.get('ResolvConfPath'),
|
|
docker_state=inspect.get('State'),
|
|
docker_volumes=inspect.get('Volumes'),
|
|
docker_volumes_rw=inspect.get('VolumesRW'),
|
|
)
|
|
|
|
hostvars[name].update(container_info)
|
|
|
|
groups['docker_hosts'] = [host.get('base_url') for host in hosts]
|
|
groups['_meta'] = dict()
|
|
groups['_meta']['hostvars'] = hostvars
|
|
print json.dumps(groups, sort_keys=True, indent=4)
|
|
sys.exit(0)
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument('--list', action='store_true')
|
|
group.add_argument('--host', action='store_true')
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
if args.list:
|
|
list_groups()
|
|
elif args.host:
|
|
write_stderr('This option is not supported.')
|
|
sys.exit(1)
|
|
sys.exit(0)
|
|
|
|
|
|
main()
|