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