1#!/usr/bin/env python
2"""
3SoftLayer external inventory script.
4
5The SoftLayer Python API client is required. Use `pip install softlayer` to install it.
6You have a few different options for configuring your username and api_key. You can pass
7environment variables (SL_USERNAME and SL_API_KEY). You can also write INI file to
8~/.softlayer or /etc/softlayer.conf. For more information see the SL API at:
9- https://softlayer-python.readthedocs.io/en/latest/config_file.html
10
11The SoftLayer Python client has a built in command for saving this configuration file
12via the command `sl config setup`.
13"""
14
15# Copyright (C) 2014  AJ Bourg <aj@ajbourg.com>
16#
17# This program is free software: you can redistribute it and/or modify
18# it under the terms of the GNU General Public License as published by
19# the Free Software Foundation, either version 3 of the License, or
20# (at your option) any later version.
21#
22# This program is distributed in the hope that it will be useful,
23# but WITHOUT ANY WARRANTY; without even the implied warranty of
24# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25# GNU General Public License for more details.
26#
27# You should have received a copy of the GNU General Public License
28# along with this program.  If not, see <http://www.gnu.org/licenses/>.
29
30#
31# I found the structure of the ec2.py script very helpful as an example
32# as I put this together. Thanks to whoever wrote that script!
33#
34
35import SoftLayer
36import re
37import argparse
38import itertools
39import json
40
41
42class SoftLayerInventory(object):
43    common_items = [
44        'id',
45        'globalIdentifier',
46        'hostname',
47        'domain',
48        'fullyQualifiedDomainName',
49        'primaryBackendIpAddress',
50        'primaryIpAddress',
51        'datacenter',
52        'tagReferences',
53        'userData.value',
54    ]
55
56    vs_items = [
57        'lastKnownPowerState.name',
58        'powerState',
59        'maxCpu',
60        'maxMemory',
61        'activeTransaction.transactionStatus[friendlyName,name]',
62        'status',
63    ]
64
65    hw_items = [
66        'hardwareStatusId',
67        'processorPhysicalCoreAmount',
68        'memoryCapacity',
69    ]
70
71    def _empty_inventory(self):
72        return {"_meta": {"hostvars": {}}}
73
74    def __init__(self):
75        '''Main path'''
76
77        self.inventory = self._empty_inventory()
78
79        self.parse_options()
80
81        if self.args.list:
82            self.get_all_servers()
83            print(self.json_format_dict(self.inventory, True))
84        elif self.args.host:
85            self.get_all_servers()
86            print(self.json_format_dict(self.inventory["_meta"]["hostvars"][self.args.host], True))
87
88    def to_safe(self, word):
89        '''Converts 'bad' characters in a string to underscores so they can be used as Ansible groups'''
90
91        return re.sub(r"[^A-Za-z0-9\-\.]", "_", word)
92
93    def push(self, my_dict, key, element):
94        '''Push an element onto an array that may not have been defined in the dict'''
95
96        if key in my_dict:
97            my_dict[key].append(element)
98        else:
99            my_dict[key] = [element]
100
101    def parse_options(self):
102        '''Parse all the arguments from the CLI'''
103
104        parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on SoftLayer')
105        parser.add_argument('--list', action='store_true', default=False,
106                            help='List instances (default: False)')
107        parser.add_argument('--host', action='store',
108                            help='Get all the variables about a specific instance')
109        self.args = parser.parse_args()
110
111    def json_format_dict(self, data, pretty=False):
112        '''Converts a dict to a JSON object and dumps it as a formatted string'''
113
114        if pretty:
115            return json.dumps(data, sort_keys=True, indent=2)
116        else:
117            return json.dumps(data)
118
119    def process_instance(self, instance, instance_type="virtual"):
120        '''Populate the inventory dictionary with any instance information'''
121
122        # only want active instances
123        if 'status' in instance and instance['status']['name'] != 'Active':
124            return
125
126        # and powered on instances
127        if 'powerState' in instance and instance['powerState']['name'] != 'Running':
128            return
129
130        # 5 is active for hardware... see https://forums.softlayer.com/forum/softlayer-developer-network/general-discussion/2955-hardwarestatusid
131        if 'hardwareStatusId' in instance and instance['hardwareStatusId'] != 5:
132            return
133
134        # if there's no IP address, we can't reach it
135        if 'primaryIpAddress' not in instance:
136            return
137
138        instance['userData'] = instance['userData'][0]['value'] if instance['userData'] else ''
139
140        dest = instance['primaryIpAddress']
141
142        instance['tags'] = list()
143        for tag in instance['tagReferences']:
144            instance['tags'].append(tag['tag']['name'])
145
146        del instance['tagReferences']
147
148        self.inventory["_meta"]["hostvars"][dest] = instance
149
150        # Inventory: group by memory
151        if 'maxMemory' in instance:
152            self.push(self.inventory, self.to_safe('memory_' + str(instance['maxMemory'])), dest)
153        elif 'memoryCapacity' in instance:
154            self.push(self.inventory, self.to_safe('memory_' + str(instance['memoryCapacity'])), dest)
155
156        # Inventory: group by cpu count
157        if 'maxCpu' in instance:
158            self.push(self.inventory, self.to_safe('cpu_' + str(instance['maxCpu'])), dest)
159        elif 'processorPhysicalCoreAmount' in instance:
160            self.push(self.inventory, self.to_safe('cpu_' + str(instance['processorPhysicalCoreAmount'])), dest)
161
162        # Inventory: group by datacenter
163        self.push(self.inventory, self.to_safe('datacenter_' + instance['datacenter']['name']), dest)
164
165        # Inventory: group by hostname
166        self.push(self.inventory, self.to_safe(instance['hostname']), dest)
167
168        # Inventory: group by FQDN
169        self.push(self.inventory, self.to_safe(instance['fullyQualifiedDomainName']), dest)
170
171        # Inventory: group by domain
172        self.push(self.inventory, self.to_safe(instance['domain']), dest)
173
174        # Inventory: group by type (hardware/virtual)
175        self.push(self.inventory, instance_type, dest)
176
177        for tag in instance['tags']:
178            self.push(self.inventory, tag, dest)
179
180    def get_virtual_servers(self):
181        '''Get all the CCI instances'''
182        vs = SoftLayer.VSManager(self.client)
183        mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.vs_items))
184        instances = vs.list_instances(mask=mask)
185
186        for instance in instances:
187            self.process_instance(instance)
188
189    def get_physical_servers(self):
190        '''Get all the hardware instances'''
191        hw = SoftLayer.HardwareManager(self.client)
192        mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.hw_items))
193        instances = hw.list_hardware(mask=mask)
194
195        for instance in instances:
196            self.process_instance(instance, 'hardware')
197
198    def get_all_servers(self):
199        self.client = SoftLayer.Client()
200        self.get_virtual_servers()
201        self.get_physical_servers()
202
203
204SoftLayerInventory()
205