From 79f2dca60a80839ab656eef656522fbef002e7af Mon Sep 17 00:00:00 2001 From: AJ Bourg Date: Fri, 23 May 2014 14:33:47 -0600 Subject: [PATCH 1/4] Initial SoftLayer API inventory client. --- plugins/inventory/softlayer.py | 163 +++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100755 plugins/inventory/softlayer.py diff --git a/plugins/inventory/softlayer.py b/plugins/inventory/softlayer.py new file mode 100755 index 00000000000..2535afc97e2 --- /dev/null +++ b/plugins/inventory/softlayer.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +""" +SoftLayer external inventory script. + +The SoftLayer Python API client is required. Use `pip install softlayer` to install it. +""" + +# Copyright (C) 2014 AJ Bourg +# +# This program 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. +# +# This program 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 this program. If not, see . + +# +# Thanks to the vagrant.py inventory script for giving me the basic structure +# of this. +# + +import SoftLayer +import sys +import subprocess +import re +import string +import argparse +try: + import json +except: + import simplejson as json + +class SoftLayerInventory(object): + def _empty_inventory(self): + return {"_meta" : {"hostvars" : {}}} + + def __init__(self): + '''Main path''' + + self.inventory = self._empty_inventory() + + self.parse_options() + + if self.args.list: + self.get_all_servers() + print self.json_format_dict(self.inventory, True) + elif self.args.host: + self.get_virtual_servers(client) + print self.json_format_dict(self.inventory["_meta"]["hostvars"][self.args.host], True) + + def to_safe(self, word): + ''' Converts 'bad' characters in a string to underscores so they can be + used as Ansible groups ''' + + return re.sub("[^A-Za-z0-9\-\.]", "_", word) + + def push(self, my_dict, key, element): + ''' Pushed an element onto an array that may not have been defined in + the dict ''' + + if key in my_dict: + my_dict[key].append(element); + else: + my_dict[key] = [element] + + def parse_options(self): + '''Parse all the arguments from the CLI''' + + parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2') + 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 json_format_dict(self, 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) + + def process_instance(self, instance, instance_type="virtual"): + '''Populate the inventory dictionary with any instance information''' + + # only want active instances + if 'status' in instance and instance['status']['name'] != 'Active': + return + + # and powered on instances + if 'powerState' in instance and instance['powerState']['name'] != 'Running': + return + + # 5 is active for hardware... see https://forums.softlayer.com/forum/softlayer-developer-network/general-discussion/2955-hardwarestatusid + if 'hardwareStatusId' in instance and instance['hardwareStatusId'] != 5: + return + + if 'primaryIpAddress' not in instance: + print instance['fullyQualifiedDomainName'] + " doesn't have an ip!" + print instance + return + + dest = instance['primaryIpAddress'] + + self.inventory["_meta"]["hostvars"][dest] = instance + + # Inventory: group by memory + if 'maxMemory' in instance: + self.push(self.inventory, self.to_safe('memory_' + str(instance['maxMemory'])), dest) + elif 'memoryCapacity' in instance: + self.push(self.inventory, self.to_safe('memory_' + str(instance['memoryCapacity'])), dest) + + # Inventory: group by cpu count + if 'maxCpu' in instance: + self.push(self.inventory, self.to_safe('cpu_' + str(instance['maxCpu'])), dest) + elif 'processorPhysicalCoreAmount' in instance: + self.push(self.inventory, self.to_safe('cpu_' + str(instance['processorPhysicalCoreAmount'])), dest) + + # Inventory: group by datacenter + self.push(self.inventory, self.to_safe('datacenter_' + instance['datacenter']['name']), dest) + + # Inventory: group by hostname + self.push(self.inventory, self.to_safe(instance['hostname']), dest) + + # Inventory: group by FQDN + self.push(self.inventory, self.to_safe(instance['fullyQualifiedDomainName']), dest) + + # Inventory: group by domain + self.push(self.inventory, self.to_safe(instance['domain']), dest) + + # Inventory: group by type (hardware/virtual) + self.push(self.inventory, instance_type, dest) + + def get_virtual_servers(self): + '''Get all the CCI instances''' + vs = SoftLayer.VSManager(self.client) + instances = vs.list_instances() + + for instance in instances: + self.process_instance(instance) + + def get_physical_servers(self): + '''Get all the hardware instances''' + hw = SoftLayer.HardwareManager(self.client) + instances = hw.list_hardware() + + for instance in instances: + self.process_instance(instance, 'hardware') + + def get_all_servers(self): + self.client = SoftLayer.Client() + self.get_virtual_servers() + self.get_physical_servers() + +SoftLayerInventory() \ No newline at end of file From 5986959597be3accb975fec5ed0b126180b069fa Mon Sep 17 00:00:00 2001 From: AJ Bourg Date: Fri, 23 May 2014 14:47:46 -0600 Subject: [PATCH 2/4] Remove some development code. EC2 -> SoftLayer --- plugins/inventory/softlayer.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/inventory/softlayer.py b/plugins/inventory/softlayer.py index 2535afc97e2..76112f22017 100755 --- a/plugins/inventory/softlayer.py +++ b/plugins/inventory/softlayer.py @@ -72,7 +72,7 @@ class SoftLayerInventory(object): def parse_options(self): '''Parse all the arguments from the CLI''' - parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2') + parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on SoftLayer') parser.add_argument('--list', action='store_true', default=False, help='List instances (default: False)') parser.add_argument('--host', action='store', @@ -103,9 +103,8 @@ class SoftLayerInventory(object): if 'hardwareStatusId' in instance and instance['hardwareStatusId'] != 5: return + # if there's no IP address, we can't reach it if 'primaryIpAddress' not in instance: - print instance['fullyQualifiedDomainName'] + " doesn't have an ip!" - print instance return dest = instance['primaryIpAddress'] @@ -160,4 +159,4 @@ class SoftLayerInventory(object): self.get_virtual_servers() self.get_physical_servers() -SoftLayerInventory() \ No newline at end of file +SoftLayerInventory() From 27e99f2a84bbc7448e831fd6929ce3859377fda7 Mon Sep 17 00:00:00 2001 From: AJ Bourg Date: Mon, 26 May 2014 16:36:32 -0600 Subject: [PATCH 3/4] Update some doc strings --- plugins/inventory/softlayer.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/inventory/softlayer.py b/plugins/inventory/softlayer.py index 76112f22017..956a8c731ba 100755 --- a/plugins/inventory/softlayer.py +++ b/plugins/inventory/softlayer.py @@ -55,14 +55,12 @@ class SoftLayerInventory(object): print self.json_format_dict(self.inventory["_meta"]["hostvars"][self.args.host], True) def to_safe(self, word): - ''' Converts 'bad' characters in a string to underscores so they can be - used as Ansible groups ''' + '''Converts 'bad' characters in a string to underscores so they can be used as Ansible groups''' return re.sub("[^A-Za-z0-9\-\.]", "_", word) def push(self, my_dict, key, element): - ''' Pushed an element onto an array that may not have been defined in - the dict ''' + '''Push an element onto an array that may not have been defined in the dict''' if key in my_dict: my_dict[key].append(element); @@ -80,8 +78,7 @@ class SoftLayerInventory(object): self.args = parser.parse_args() def json_format_dict(self, data, pretty=False): - ''' Converts a dict to a JSON object and dumps it as a formatted - string ''' + '''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) From a9ba02ff048503fd0de70296eeef056997ce4956 Mon Sep 17 00:00:00 2001 From: AJ Bourg Date: Mon, 26 May 2014 16:57:41 -0600 Subject: [PATCH 4/4] Update dependencies. Add some doc notes. --- plugins/inventory/softlayer.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/inventory/softlayer.py b/plugins/inventory/softlayer.py index 956a8c731ba..ef8a2f6a740 100755 --- a/plugins/inventory/softlayer.py +++ b/plugins/inventory/softlayer.py @@ -3,6 +3,13 @@ SoftLayer external inventory script. The SoftLayer Python API client is required. Use `pip install softlayer` to install it. +You have a few different options for configuring your username and api_key. You can pass +environment variables (SL_USERNAME and SL_API_KEY). You can also write INI file to +~/.softlayer or /etc/softlayer.conf. For more information see the SL API at: +- https://softlayer-python.readthedocs.org/en/latest/config_file.html + +The SoftLayer Python client has a built in command for saving this configuration file +via the command `sl config setup`. """ # Copyright (C) 2014 AJ Bourg @@ -21,15 +28,12 @@ The SoftLayer Python API client is required. Use `pip install softlayer` to inst # along with this program. If not, see . # -# Thanks to the vagrant.py inventory script for giving me the basic structure -# of this. +# I found the structure of the ec2.py script very helpful as an example +# as I put this together. Thanks to whoever wrote that script! # import SoftLayer -import sys -import subprocess import re -import string import argparse try: import json