From 6635782ed84027fd599cd62ec84747db9b499467 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Tue, 5 Apr 2016 14:43:25 -0400 Subject: [PATCH] add API support for CloudForms inventory (#1099) --- awx/main/constants.py | 2 +- awx/main/models/base.py | 2 +- awx/main/models/credential.py | 1 + awx/main/models/inventory.py | 1 + awx/main/tasks.py | 12 ++ awx/plugins/inventory/cloudforms.ini.example | 16 +++ awx/plugins/inventory/cloudforms.py | 126 +++++++++++++++++++ 7 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 awx/plugins/inventory/cloudforms.ini.example create mode 100644 awx/plugins/inventory/cloudforms.py diff --git a/awx/main/constants.py b/awx/main/constants.py index 3e389d162d..ba65f7a715 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -1,5 +1,5 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. -CLOUD_PROVIDERS = ('azure', 'ec2', 'gce', 'rax', 'vmware', 'openstack', 'foreman') +CLOUD_PROVIDERS = ('azure', 'ec2', 'gce', 'rax', 'vmware', 'openstack', 'foreman', 'cloudforms') SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom',) diff --git a/awx/main/models/base.py b/awx/main/models/base.py index 8bedb37421..243d7612e8 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -61,7 +61,7 @@ PERMISSION_TYPE_CHOICES = [ (PERM_JOBTEMPLATE_CREATE, _('Create a Job Template')), ] -CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'openstack', 'custom', 'foreman'] +CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'openstack', 'custom', 'foreman', 'cloudforms'] VERBOSITY_CHOICES = [ (0, '0 (Normal)'), diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index 04235948d7..9cfcc50d54 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -38,6 +38,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): ('rax', _('Rackspace')), ('vmware', _('VMware vCenter')), ('foreman', _('Satellite 6')), + ('cloudforms', _('CloudForms')), ('gce', _('Google Compute Engine')), ('azure', _('Microsoft Azure')), ('openstack', _('OpenStack')), diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 9f9659322b..28176c0831 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -733,6 +733,7 @@ class InventorySourceOptions(BaseModel): ('azure', _('Microsoft Azure')), ('vmware', _('VMware vCenter')), ('foreman', _('Satellite 6')), + ('cloudforms', _('CloudForms')), ('openstack', _('OpenStack')), ('custom', _('Custom Script')), ] diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 719938f2d6..ed7b058ef5 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1239,6 +1239,16 @@ class RunInventoryUpdate(BaseTask): cp.set(section, 'path', '/tmp') cp.set(section, 'max_age', '0') + elif inventory_update.source == 'cloudforms': + section = 'cloudforms' + cp.add_section(section) + + credential = inventory_update.credential + if credential: + cp.set(section, 'hostname', credential.host) + cp.set(section, 'username', credential.username) + cp.set(section, 'password', decrypt_field(credential, 'password')) + # Return INI content. if cp.sections(): f = cStringIO.StringIO() @@ -1321,6 +1331,8 @@ class RunInventoryUpdate(BaseTask): env['OS_CLIENT_CONFIG_FILE'] = cloud_credential elif inventory_update.source == 'foreman': env['FOREMAN_INI_PATH'] = cloud_credential + elif inventory_update.source == 'cloudforms': + env['CLOUDFORMS_INI_PATH'] = cloud_credential elif inventory_update.source == 'file': # FIXME: Parse source_env to dict, update env. pass diff --git a/awx/plugins/inventory/cloudforms.ini.example b/awx/plugins/inventory/cloudforms.ini.example new file mode 100644 index 0000000000..771fdf94c2 --- /dev/null +++ b/awx/plugins/inventory/cloudforms.ini.example @@ -0,0 +1,16 @@ +# Ansible CloudForms external inventory script settings +# + +[cloudforms] + +# The version of CloudForms (this is not used yet) +version = 3.1 + +# The hostname of the CloudForms server +hostname = #insert your hostname here + +# Username for CloudForms +username = #insert your cloudforms user here + +# Password for CloudForms user +password = #password diff --git a/awx/plugins/inventory/cloudforms.py b/awx/plugins/inventory/cloudforms.py new file mode 100644 index 0000000000..3de81d0bd2 --- /dev/null +++ b/awx/plugins/inventory/cloudforms.py @@ -0,0 +1,126 @@ +#!/usr/bin/python + +''' +CloudForms external inventory script +================================================== +Generates inventory that Ansible can understand by making API request to CloudForms. +Modeled after https://raw.githubusercontent.com/ansible/ansible/stable-1.9/plugins/inventory/ec2.py +jlabocki redhat.com or @jameslabocki on twitter +''' + +import os +import argparse +import ConfigParser +import requests +import json + +# This disables warnings and is not a good idea, but hey, this is a demo +# http://urllib3.readthedocs.org/en/latest/security.html#disabling-warnings +requests.packages.urllib3.disable_warnings() + +class CloudFormsInventory(object): + + def _empty_inventory(self): + return {"_meta" : {"hostvars" : {}}} + + def __init__(self): + ''' Main execution path ''' + + # Inventory grouped by instance IDs, tags, security groups, regions, + # and availability zones + self.inventory = self._empty_inventory() + + # Index of hostname (address) to instance ID + self.index = {} + + # Read CLI arguments + self.read_settings() + self.parse_cli_args() + + # Get Hosts + if self.args.list: + self.get_hosts() + + # This doesn't exist yet and needs to be added + if self.args.host: + data2 = { } + print json.dumps(data2, indent=2) + + def parse_cli_args(self): + ''' Command line argument processing ''' + + parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on CloudForms') + parser.add_argument('--list', action='store_true', default=False, + help='List instances (default: False)') + parser.add_argument('--host', action='store', + help='Get all the variables about a specific instance') + self.args = parser.parse_args() + + def read_settings(self): + ''' Reads the settings from the cloudforms.ini file ''' + + config = ConfigParser.SafeConfigParser() + config_paths = [ + os.path.join(os.path.dirname(os.path.realpath(__file__)), 'cloudforms.ini'), + "/opt/rh/cloudforms.ini", + ] + + env_value = os.environ.get('CLOUDFORMS_INI_PATH') + if env_value is not None: + config_paths.append(os.path.expanduser(os.path.expandvars(env_value))) + + config.read(config_paths) + + # Version + if config.has_option('cloudforms', 'version'): + self.cloudforms_version = config.get('cloudforms', 'version') + else: + self.cloudforms_version = "none" + + # CloudForms Endpoint + if config.has_option('cloudforms', 'hostname'): + self.cloudforms_hostname = config.get('cloudforms', 'hostname') + else: + self.cloudforms_hostname = None + + # CloudForms Username + if config.has_option('cloudforms', 'username'): + self.cloudforms_username = config.get('cloudforms', 'username') + else: + self.cloudforms_username = "none" + + # CloudForms Password + if config.has_option('cloudforms', 'password'): + self.cloudforms_password = config.get('cloudforms', 'password') + else: + self.cloudforms_password = "none" + + def get_hosts(self): + ''' Gets host from CloudForms ''' + r = requests.get("https://" + self.cloudforms_hostname + "/api/vms?expand=resources&attributes=name,power_state", auth=(self.cloudforms_username,self.cloudforms_password), verify=False) + + obj = r.json() + + #Remove objects that don't matter + del obj["count"] + del obj["subcount"] + del obj["name"] + + #Create a new list to grab VMs with power_state on to add to a new list + #I'm sure there is a cleaner way to do this + newlist = [] + getnext = False + for x in obj.items(): + for y in x[1]: + for z in y.items(): + if getnext == True: + newlist.append(z[1]) + getnext = False + if ( z[0] == "power_state" and z[1] == "on" ): + getnext = True + newdict = {'hosts': newlist} + newdict2 = {'Dynamic_CloudForms': newdict} + print json.dumps(newdict2, indent=2) + +# Run the script +CloudFormsInventory()