Merge pull request #7025 from angstwad/docker_inv
Add Docker inventory plugin
This commit is contained in:
commit
442c68f7e8
2 changed files with 493 additions and 0 deletions
359
plugins/inventory/docker.py
Executable file
359
plugins/inventory/docker.py
Executable file
|
@ -0,0 +1,359 @@
|
|||
#!/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 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)
|
||||
|
||||
# Enviroment 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):
|
||||
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()
|
134
plugins/inventory/docker.yml
Normal file
134
plugins/inventory/docker.yml
Normal file
|
@ -0,0 +1,134 @@
|
|||
# This is the configuration file for the Ansible plugin for Docker inventory.
|
||||
#
|
||||
# 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 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
|
||||
#
|
||||
#
|
||||
#
|
||||
# The Docker inventory plugin provides several enviroment variables that
|
||||
# may be overridden here. This configuration file always takes precedence
|
||||
# over environment variables.
|
||||
#
|
||||
# Variable precedence is: hosts > defaults > environment
|
||||
|
||||
---
|
||||
defaults:
|
||||
host: unix:///var/run/docker.sock
|
||||
version: 1.9
|
||||
timeout: 60
|
||||
private_ssh_port: 22
|
||||
default_ip: 127.0.0.1
|
||||
hosts:
|
||||
# - host: tcp://10.45.5.16:4243
|
||||
# version: 1.9
|
||||
# timeout: 60
|
||||
# private_ssh_port: 2022
|
||||
# default_ip: 172.16.3.45
|
||||
# - host: tcp://localhost:4243
|
Loading…
Reference in a new issue