Merge pull request #11161 from joshainglis/ovirt-dynamic-inventory
oVirt Dynamic Inventory
This commit is contained in:
commit
ca89467e37
2 changed files with 320 additions and 0 deletions
33
plugins/inventory/ovirt.ini
Normal file
33
plugins/inventory/ovirt.ini
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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: Josha Inglis <jinglis@iix.net> based on the gce.ini by Eric Johnson <erjohnso@google.com>
|
||||||
|
|
||||||
|
[ovirt]
|
||||||
|
# ovirt Service Account configuration information can be stored in the
|
||||||
|
# libcloud 'secrets.py' file. Ideally, the 'secrets.py' file will already
|
||||||
|
# exist in your PYTHONPATH and be picked up automatically with an import
|
||||||
|
# statement in the inventory script. However, you can specify an absolute
|
||||||
|
# path to the secrets.py file with 'libcloud_secrets' parameter.
|
||||||
|
ovirt_api_secrets =
|
||||||
|
|
||||||
|
# If you are not going to use a 'secrets.py' file, you can set the necessary
|
||||||
|
# authorization parameters here.
|
||||||
|
ovirt_url =
|
||||||
|
ovirt_username =
|
||||||
|
ovirt_password =
|
287
plugins/inventory/ovirt.py
Executable file
287
plugins/inventory/ovirt.py
Executable file
|
@ -0,0 +1,287 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright 2015 IIX Inc.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
ovirt external inventory script
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Generates inventory that Ansible can understand by making API requests to
|
||||||
|
oVirt via the ovirt-engine-sdk-python library.
|
||||||
|
|
||||||
|
When run against a specific host, this script returns the following variables
|
||||||
|
based on the data obtained from the ovirt_sdk Node object:
|
||||||
|
- ovirt_uuid
|
||||||
|
- ovirt_id
|
||||||
|
- ovirt_image
|
||||||
|
- ovirt_machine_type
|
||||||
|
- ovirt_ips
|
||||||
|
- ovirt_name
|
||||||
|
- ovirt_description
|
||||||
|
- ovirt_status
|
||||||
|
- ovirt_zone
|
||||||
|
- ovirt_tags
|
||||||
|
- ovirt_stats
|
||||||
|
|
||||||
|
When run in --list mode, instances are grouped by the following categories:
|
||||||
|
|
||||||
|
- zone:
|
||||||
|
zone group name.
|
||||||
|
- instance tags:
|
||||||
|
An entry is created for each tag. For example, if you have two instances
|
||||||
|
with a common tag called 'foo', they will both be grouped together under
|
||||||
|
the 'tag_foo' name.
|
||||||
|
- network name:
|
||||||
|
the name of the network is appended to 'network_' (e.g. the 'default'
|
||||||
|
network will result in a group named 'network_default')
|
||||||
|
- running status:
|
||||||
|
group name prefixed with 'status_' (e.g. status_up, status_down,..)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Execute uname on all instances in the us-central1-a zone
|
||||||
|
$ ansible -i ovirt.py us-central1-a -m shell -a "/bin/uname -a"
|
||||||
|
|
||||||
|
Use the ovirt inventory script to print out instance specific information
|
||||||
|
$ plugins/inventory/ovirt.py --host my_instance
|
||||||
|
|
||||||
|
Author: Josha Inglis <jinglis@iix.net> based on the gce.py by Eric Johnson <erjohnso@google.com>
|
||||||
|
Version: 0.0.1
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_AGENT_PRODUCT = "Ansible-ovirt_inventory_plugin"
|
||||||
|
USER_AGENT_VERSION = "v1"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import ConfigParser
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
from ovirtsdk.api import API
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
from ovirtsdk.xml import params
|
||||||
|
except ImportError:
|
||||||
|
print("ovirt inventory script requires ovirt-engine-sdk-python")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class OVirtInventory(object):
|
||||||
|
def __init__(self):
|
||||||
|
# Read settings and parse CLI arguments
|
||||||
|
self.args = self.parse_cli_args()
|
||||||
|
self.driver = self.get_ovirt_driver()
|
||||||
|
|
||||||
|
# Just display data for specific host
|
||||||
|
if self.args.host:
|
||||||
|
print self.json_format_dict(
|
||||||
|
self.node_to_dict(self.get_instance(self.args.host)),
|
||||||
|
pretty=self.args.pretty
|
||||||
|
)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Otherwise, assume user wants all instances grouped
|
||||||
|
print(
|
||||||
|
self.json_format_dict(
|
||||||
|
data=self.group_instances(),
|
||||||
|
pretty=self.args.pretty
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ovirt_driver():
|
||||||
|
"""
|
||||||
|
Determine the ovirt authorization settings and return a ovirt_sdk driver.
|
||||||
|
|
||||||
|
:rtype : ovirtsdk.api.API
|
||||||
|
"""
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
ovirt_ini_default_path = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)), "ovirt.ini")
|
||||||
|
ovirt_ini_path = os.environ.get('OVIRT_INI_PATH', ovirt_ini_default_path)
|
||||||
|
|
||||||
|
# Create a ConfigParser.
|
||||||
|
# This provides empty defaults to each key, so that environment
|
||||||
|
# variable configuration (as opposed to INI configuration) is able
|
||||||
|
# to work.
|
||||||
|
config = ConfigParser.SafeConfigParser(defaults={
|
||||||
|
'ovirt_url': '',
|
||||||
|
'ovirt_username': '',
|
||||||
|
'ovirt_password': '',
|
||||||
|
'ovirt_api_secrets': '',
|
||||||
|
})
|
||||||
|
if 'ovirt' not in config.sections():
|
||||||
|
config.add_section('ovirt')
|
||||||
|
config.read(ovirt_ini_path)
|
||||||
|
|
||||||
|
# Attempt to get ovirt params from a configuration file, if one
|
||||||
|
# exists.
|
||||||
|
secrets_path = config.get('ovirt', 'ovirt_api_secrets')
|
||||||
|
secrets_found = False
|
||||||
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {})
|
||||||
|
secrets_found = True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not secrets_found and secrets_path:
|
||||||
|
if not secrets_path.endswith('secrets.py'):
|
||||||
|
err = "Must specify ovirt_sdk secrets file as /absolute/path/to/secrets.py"
|
||||||
|
print(err)
|
||||||
|
sys.exit(1)
|
||||||
|
sys.path.append(os.path.dirname(secrets_path))
|
||||||
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {})
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
if not secrets_found:
|
||||||
|
kwargs = {
|
||||||
|
'url': config.get('ovirt', 'ovirt_url'),
|
||||||
|
'username': config.get('ovirt', 'ovirt_username'),
|
||||||
|
'password': config.get('ovirt', 'ovirt_password'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the appropriate environment variables are set, they override
|
||||||
|
# other configuration; process those into our args and kwargs.
|
||||||
|
kwargs['url'] = os.environ.get('OVIRT_URL')
|
||||||
|
kwargs['username'] = os.environ.get('OVIRT_EMAIL')
|
||||||
|
kwargs['password'] = os.environ.get('OVIRT_PASS')
|
||||||
|
|
||||||
|
# Retrieve and return the ovirt driver.
|
||||||
|
return API(insecure=True, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_cli_args():
|
||||||
|
"""
|
||||||
|
Command line argument processing
|
||||||
|
|
||||||
|
:rtype : argparse.Namespace
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on ovirt')
|
||||||
|
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
|
||||||
|
parser.add_argument('--host', action='store', help='Get all information about an instance')
|
||||||
|
parser.add_argument('--pretty', action='store_true', default=False, help='Pretty format (default: False)')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def node_to_dict(self, inst):
|
||||||
|
"""
|
||||||
|
:type inst: params.VM
|
||||||
|
"""
|
||||||
|
if inst is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
inst.get_custom_properties()
|
||||||
|
ips = [ip.get_address() for ip in inst.get_guest_info().get_ips().get_ip()] \
|
||||||
|
if inst.get_guest_info() is not None else []
|
||||||
|
stats = {}
|
||||||
|
for stat in inst.get_statistics().list():
|
||||||
|
stats[stat.get_name()] = stat.get_values().get_value()[0].get_datum()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'ovirt_uuid': inst.get_id(),
|
||||||
|
'ovirt_id': inst.get_id(),
|
||||||
|
'ovirt_image': inst.get_os().get_type(),
|
||||||
|
'ovirt_machine_type': inst.get_instance_type(),
|
||||||
|
'ovirt_ips': ips,
|
||||||
|
'ovirt_name': inst.get_name(),
|
||||||
|
'ovirt_description': inst.get_description(),
|
||||||
|
'ovirt_status': inst.get_status().get_state(),
|
||||||
|
'ovirt_zone': inst.get_cluster().get_id(),
|
||||||
|
'ovirt_tags': self.get_tags(inst),
|
||||||
|
'ovirt_stats': stats,
|
||||||
|
# Hosts don't have a public name, so we add an IP
|
||||||
|
'ansible_ssh_host': ips[0] if len(ips) > 0 else None
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_tags(inst):
|
||||||
|
"""
|
||||||
|
:type inst: params.VM
|
||||||
|
"""
|
||||||
|
return [x.get_name() for x in inst.get_tags().list()]
|
||||||
|
|
||||||
|
# noinspection PyBroadException,PyUnusedLocal
|
||||||
|
def get_instance(self, instance_name):
|
||||||
|
"""Gets details about a specific instance """
|
||||||
|
try:
|
||||||
|
return self.driver.vms.get(name=instance_name)
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def group_instances(self):
|
||||||
|
"""Group all instances"""
|
||||||
|
groups = defaultdict(list)
|
||||||
|
meta = {"hostvars": {}}
|
||||||
|
|
||||||
|
for node in self.driver.vms.list():
|
||||||
|
assert isinstance(node, params.VM)
|
||||||
|
name = node.get_name()
|
||||||
|
|
||||||
|
meta["hostvars"][name] = self.node_to_dict(node)
|
||||||
|
|
||||||
|
zone = node.get_cluster().get_name()
|
||||||
|
groups[zone].append(name)
|
||||||
|
|
||||||
|
tags = self.get_tags(node)
|
||||||
|
for t in tags:
|
||||||
|
tag = 'tag_%s' % t
|
||||||
|
groups[tag].append(name)
|
||||||
|
|
||||||
|
nets = [x.get_name() for x in node.get_nics().list()]
|
||||||
|
for net in nets:
|
||||||
|
net = 'network_%s' % net
|
||||||
|
groups[net].append(name)
|
||||||
|
|
||||||
|
status = node.get_status().get_state()
|
||||||
|
stat = 'status_%s' % status.lower()
|
||||||
|
if stat in groups:
|
||||||
|
groups[stat].append(name)
|
||||||
|
else:
|
||||||
|
groups[stat] = [name]
|
||||||
|
|
||||||
|
groups["_meta"] = meta
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def json_format_dict(data, pretty=False):
|
||||||
|
""" Converts a dict to a JSON object and dumps it as a formatted
|
||||||
|
string """
|
||||||
|
|
||||||
|
if pretty:
|
||||||
|
return json.dumps(data, sort_keys=True, indent=2)
|
||||||
|
else:
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
# Run the script
|
||||||
|
OVirtInventory()
|
Loading…
Reference in a new issue