2014-05-23 22:33:47 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
"""
|
|
|
|
SoftLayer external inventory script.
|
|
|
|
|
|
|
|
The SoftLayer Python API client is required. Use `pip install softlayer` to install it.
|
2017-01-28 00:20:31 +01:00
|
|
|
You have a few different options for configuring your username and api_key. You can pass
|
2014-05-27 00:57:41 +02:00
|
|
|
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:
|
2018-06-18 14:22:50 +02:00
|
|
|
- https://softlayer-python.readthedocs.io/en/latest/config_file.html
|
2014-05-27 00:57:41 +02:00
|
|
|
|
|
|
|
The SoftLayer Python client has a built in command for saving this configuration file
|
|
|
|
via the command `sl config setup`.
|
2014-05-23 22:33:47 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
# Copyright (C) 2014 AJ Bourg <aj@ajbourg.com>
|
|
|
|
#
|
|
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#
|
2014-05-27 00:57:41 +02:00
|
|
|
# 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!
|
2014-05-23 22:33:47 +02:00
|
|
|
#
|
|
|
|
|
|
|
|
import SoftLayer
|
|
|
|
import re
|
|
|
|
import argparse
|
2015-04-21 14:32:34 +02:00
|
|
|
import itertools
|
2014-05-23 22:33:47 +02:00
|
|
|
try:
|
|
|
|
import json
|
|
|
|
except:
|
|
|
|
import simplejson as json
|
|
|
|
|
2017-05-09 23:38:08 +02:00
|
|
|
|
2014-05-23 22:33:47 +02:00
|
|
|
class SoftLayerInventory(object):
|
2015-04-21 14:32:34 +02:00
|
|
|
common_items = [
|
|
|
|
'id',
|
|
|
|
'globalIdentifier',
|
|
|
|
'hostname',
|
|
|
|
'domain',
|
|
|
|
'fullyQualifiedDomainName',
|
|
|
|
'primaryBackendIpAddress',
|
|
|
|
'primaryIpAddress',
|
|
|
|
'datacenter',
|
|
|
|
'tagReferences.tag.name',
|
2017-08-04 19:28:40 +02:00
|
|
|
'userData.value',
|
2017-05-09 23:38:08 +02:00
|
|
|
]
|
2015-04-21 14:32:34 +02:00
|
|
|
|
|
|
|
vs_items = [
|
|
|
|
'lastKnownPowerState.name',
|
|
|
|
'powerState',
|
|
|
|
'maxCpu',
|
|
|
|
'maxMemory',
|
|
|
|
'activeTransaction.transactionStatus[friendlyName,name]',
|
|
|
|
'status',
|
2017-05-09 23:38:08 +02:00
|
|
|
]
|
2015-04-21 14:32:34 +02:00
|
|
|
|
|
|
|
hw_items = [
|
|
|
|
'hardwareStatusId',
|
|
|
|
'processorPhysicalCoreAmount',
|
|
|
|
'memoryCapacity',
|
2017-05-09 23:38:08 +02:00
|
|
|
]
|
2015-04-21 14:32:34 +02:00
|
|
|
|
2014-05-23 22:33:47 +02:00
|
|
|
def _empty_inventory(self):
|
2017-05-09 23:38:08 +02:00
|
|
|
return {"_meta": {"hostvars": {}}}
|
2014-05-23 22:33:47 +02:00
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
'''Main path'''
|
|
|
|
|
|
|
|
self.inventory = self._empty_inventory()
|
|
|
|
|
|
|
|
self.parse_options()
|
|
|
|
|
|
|
|
if self.args.list:
|
|
|
|
self.get_all_servers()
|
2015-08-28 08:18:13 +02:00
|
|
|
print(self.json_format_dict(self.inventory, True))
|
2014-05-23 22:33:47 +02:00
|
|
|
elif self.args.host:
|
2015-04-19 18:18:52 +02:00
|
|
|
self.get_virtual_servers()
|
2015-08-28 08:18:13 +02:00
|
|
|
print(self.json_format_dict(self.inventory["_meta"]["hostvars"][self.args.host], True))
|
2014-05-23 22:33:47 +02:00
|
|
|
|
|
|
|
def to_safe(self, word):
|
2014-05-27 00:36:32 +02:00
|
|
|
'''Converts 'bad' characters in a string to underscores so they can be used as Ansible groups'''
|
2014-05-23 22:33:47 +02:00
|
|
|
|
2017-11-21 04:08:30 +01:00
|
|
|
return re.sub(r"[^A-Za-z0-9\-\.]", "_", word)
|
2014-05-23 22:33:47 +02:00
|
|
|
|
|
|
|
def push(self, my_dict, key, element):
|
2014-05-27 00:36:32 +02:00
|
|
|
'''Push an element onto an array that may not have been defined in the dict'''
|
2014-05-23 22:33:47 +02:00
|
|
|
|
|
|
|
if key in my_dict:
|
2017-01-28 09:12:11 +01:00
|
|
|
my_dict[key].append(element)
|
2014-05-23 22:33:47 +02:00
|
|
|
else:
|
|
|
|
my_dict[key] = [element]
|
|
|
|
|
|
|
|
def parse_options(self):
|
|
|
|
'''Parse all the arguments from the CLI'''
|
|
|
|
|
2014-05-23 22:47:46 +02:00
|
|
|
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on SoftLayer')
|
2014-05-23 22:33:47 +02:00
|
|
|
parser.add_argument('--list', action='store_true', default=False,
|
2017-05-09 23:38:08 +02:00
|
|
|
help='List instances (default: False)')
|
2014-05-23 22:33:47 +02:00
|
|
|
parser.add_argument('--host', action='store',
|
2017-05-09 23:38:08 +02:00
|
|
|
help='Get all the variables about a specific instance')
|
2014-05-23 22:33:47 +02:00
|
|
|
self.args = parser.parse_args()
|
|
|
|
|
|
|
|
def json_format_dict(self, data, pretty=False):
|
2014-05-27 00:36:32 +02:00
|
|
|
'''Converts a dict to a JSON object and dumps it as a formatted string'''
|
2014-05-23 22:33:47 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2014-05-23 22:47:46 +02:00
|
|
|
# if there's no IP address, we can't reach it
|
2014-05-23 22:33:47 +02:00
|
|
|
if 'primaryIpAddress' not in instance:
|
|
|
|
return
|
|
|
|
|
2017-08-04 19:28:40 +02:00
|
|
|
instance['userData'] = instance['userData'][0]['value'] if instance['userData'] else ''
|
|
|
|
|
2014-05-23 22:33:47 +02:00
|
|
|
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)
|
|
|
|
|
2015-04-21 14:32:34 +02:00
|
|
|
# Inventory: group by tag
|
|
|
|
for tag in instance['tagReferences']:
|
2017-01-31 00:01:47 +01:00
|
|
|
self.push(self.inventory, tag['tag']['name'], dest)
|
2015-04-21 14:32:34 +02:00
|
|
|
|
2014-05-23 22:33:47 +02:00
|
|
|
def get_virtual_servers(self):
|
|
|
|
'''Get all the CCI instances'''
|
|
|
|
vs = SoftLayer.VSManager(self.client)
|
2017-05-09 23:38:08 +02:00
|
|
|
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.vs_items))
|
2015-04-21 14:32:34 +02:00
|
|
|
instances = vs.list_instances(mask=mask)
|
2014-05-23 22:33:47 +02:00
|
|
|
|
|
|
|
for instance in instances:
|
|
|
|
self.process_instance(instance)
|
|
|
|
|
|
|
|
def get_physical_servers(self):
|
|
|
|
'''Get all the hardware instances'''
|
|
|
|
hw = SoftLayer.HardwareManager(self.client)
|
2017-05-09 23:38:08 +02:00
|
|
|
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.hw_items))
|
2015-04-21 14:32:34 +02:00
|
|
|
instances = hw.list_hardware(mask=mask)
|
2014-05-23 22:33:47 +02:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2018-07-29 13:46:06 +02:00
|
|
|
|
2014-05-23 22:47:46 +02:00
|
|
|
SoftLayerInventory()
|