1#!/usr/bin/env python 2 3""" 4Spacewalk external inventory script 5================================= 6 7Ansible has a feature where instead of reading from /usr/local/etc/ansible/hosts 8as a text file, it can query external programs to obtain the list 9of hosts, groups the hosts are in, and even variables to assign to each host. 10 11To use this, copy this file over /usr/local/etc/ansible/hosts and chmod +x the file. 12This, more or less, allows you to keep one central database containing 13info about all of your managed instances. 14 15This script is dependent upon the spacealk-reports package being installed 16on the same machine. It is basically a CSV-to-JSON converter from the 17output of "spacewalk-report system-groups-systems|inventory". 18 19Tested with Ansible 1.9.2 and spacewalk 2.3 20""" 21# 22# Author:: Jon Miller <jonEbird@gmail.com> 23# Copyright:: Copyright (c) 2013, Jon Miller 24# 25# Extended for support of multiple organizations and 26# adding the "_meta" dictionary to --list output by 27# Bernhard Lichtinger <bernhard.lichtinger@lrz.de> 2015 28# 29# This program is free software: you can redistribute it and/or modify 30# it under the terms of the GNU General Public License as published by 31# the Free Software Foundation, either version 2 of the License, or (at 32# your option) any later version. 33# 34# This program is distributed in the hope that it will be useful, but 35# WITHOUT ANY WARRANTY; without even the implied warranty of 36# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 37# General Public License for more details. 38# 39# You should have received a copy of the GNU General Public License 40# along with this program. If not, see <http://www.gnu.org/licenses/>. 41# 42 43from __future__ import print_function 44 45import sys 46import os 47import time 48from optparse import OptionParser 49import subprocess 50import json 51 52from ansible.module_utils.six import iteritems 53from ansible.module_utils.six.moves import configparser as ConfigParser 54 55 56base_dir = os.path.dirname(os.path.realpath(__file__)) 57default_ini_file = os.path.join(base_dir, "spacewalk.ini") 58 59SW_REPORT = '/usr/bin/spacewalk-report' 60CACHE_DIR = os.path.join(base_dir, ".spacewalk_reports") 61CACHE_AGE = 300 # 5min 62INI_FILE = os.path.expanduser(os.path.expandvars(os.environ.get("SPACEWALK_INI_PATH", default_ini_file))) 63 64 65# Sanity check 66if not os.path.exists(SW_REPORT): 67 print('Error: %s is required for operation.' % (SW_REPORT), file=sys.stderr) 68 sys.exit(1) 69 70# Pre-startup work 71if not os.path.exists(CACHE_DIR): 72 os.mkdir(CACHE_DIR) 73 os.chmod(CACHE_DIR, 0o2775) 74 75# Helper functions 76# ------------------------------ 77 78 79def spacewalk_report(name): 80 """Yield a dictionary form of each CSV output produced by the specified 81 spacewalk-report 82 """ 83 cache_filename = os.path.join(CACHE_DIR, name) 84 if not os.path.exists(cache_filename) or \ 85 (time.time() - os.stat(cache_filename).st_mtime) > CACHE_AGE: 86 # Update the cache 87 fh = open(cache_filename, 'w') 88 p = subprocess.Popen([SW_REPORT, name], stdout=fh) 89 p.wait() 90 fh.close() 91 92 lines = open(cache_filename, 'r').readlines() 93 keys = lines[0].strip().split(',') 94 # add 'spacewalk_' prefix to the keys 95 keys = ['spacewalk_' + key for key in keys] 96 for line in lines[1:]: 97 values = line.strip().split(',') 98 if len(keys) == len(values): 99 yield dict(zip(keys, values)) 100 101 102# Options 103# ------------------------------ 104 105parser = OptionParser(usage="%prog [options] --list | --host <machine>") 106parser.add_option('--list', default=False, dest="list", action="store_true", 107 help="Produce a JSON consumable grouping of servers for Ansible") 108parser.add_option('--host', default=None, dest="host", 109 help="Generate additional host specific details for given host for Ansible") 110parser.add_option('-H', '--human', dest="human", 111 default=False, action="store_true", 112 help="Produce a friendlier version of either server list or host detail") 113parser.add_option('-o', '--org', default=None, dest="org_number", 114 help="Limit to spacewalk organization number") 115parser.add_option('-p', default=False, dest="prefix_org_name", action="store_true", 116 help="Prefix the group name with the organization number") 117(options, args) = parser.parse_args() 118 119 120# read spacewalk.ini if present 121# ------------------------------ 122if os.path.exists(INI_FILE): 123 config = ConfigParser.SafeConfigParser() 124 config.read(INI_FILE) 125 if config.has_option('spacewalk', 'cache_age'): 126 CACHE_AGE = config.get('spacewalk', 'cache_age') 127 if not options.org_number and config.has_option('spacewalk', 'org_number'): 128 options.org_number = config.get('spacewalk', 'org_number') 129 if not options.prefix_org_name and config.has_option('spacewalk', 'prefix_org_name'): 130 options.prefix_org_name = config.getboolean('spacewalk', 'prefix_org_name') 131 132 133# Generate dictionary for mapping group_id to org_id 134# ------------------------------ 135org_groups = {} 136try: 137 for group in spacewalk_report('system-groups'): 138 org_groups[group['spacewalk_group_id']] = group['spacewalk_org_id'] 139 140except (OSError) as e: 141 print('Problem executing the command "%s system-groups": %s' % 142 (SW_REPORT, str(e)), file=sys.stderr) 143 sys.exit(2) 144 145 146# List out the known server from Spacewalk 147# ------------------------------ 148if options.list: 149 150 # to build the "_meta"-Group with hostvars first create dictionary for later use 151 host_vars = {} 152 try: 153 for item in spacewalk_report('inventory'): 154 host_vars[item['spacewalk_profile_name']] = dict((key, (value.split(';') if ';' in value else value)) for key, value in item.items()) 155 156 except (OSError) as e: 157 print('Problem executing the command "%s inventory": %s' % 158 (SW_REPORT, str(e)), file=sys.stderr) 159 sys.exit(2) 160 161 groups = {} 162 meta = {"hostvars": {}} 163 try: 164 for system in spacewalk_report('system-groups-systems'): 165 # first get org_id of system 166 org_id = org_groups[system['spacewalk_group_id']] 167 168 # shall we add the org_id as prefix to the group name: 169 if options.prefix_org_name: 170 prefix = org_id + "-" 171 group_name = prefix + system['spacewalk_group_name'] 172 else: 173 group_name = system['spacewalk_group_name'] 174 175 # if we are limited to one organization: 176 if options.org_number: 177 if org_id == options.org_number: 178 if group_name not in groups: 179 groups[group_name] = set() 180 181 groups[group_name].add(system['spacewalk_server_name']) 182 if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta["hostvars"]: 183 meta["hostvars"][system['spacewalk_server_name']] = host_vars[system['spacewalk_server_name']] 184 # or we list all groups and systems: 185 else: 186 if group_name not in groups: 187 groups[group_name] = set() 188 189 groups[group_name].add(system['spacewalk_server_name']) 190 if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta["hostvars"]: 191 meta["hostvars"][system['spacewalk_server_name']] = host_vars[system['spacewalk_server_name']] 192 193 except (OSError) as e: 194 print('Problem executing the command "%s system-groups-systems": %s' % 195 (SW_REPORT, str(e)), file=sys.stderr) 196 sys.exit(2) 197 198 if options.human: 199 for group, systems in iteritems(groups): 200 print('[%s]\n%s\n' % (group, '\n'.join(systems))) 201 else: 202 final = dict([(k, list(s)) for k, s in iteritems(groups)]) 203 final["_meta"] = meta 204 print(json.dumps(final)) 205 # print(json.dumps(groups)) 206 sys.exit(0) 207 208 209# Return a details information concerning the spacewalk server 210# ------------------------------ 211elif options.host: 212 213 host_details = {} 214 try: 215 for system in spacewalk_report('inventory'): 216 if system['spacewalk_hostname'] == options.host: 217 host_details = system 218 break 219 220 except (OSError) as e: 221 print('Problem executing the command "%s inventory": %s' % 222 (SW_REPORT, str(e)), file=sys.stderr) 223 sys.exit(2) 224 225 if options.human: 226 print('Host: %s' % options.host) 227 for k, v in iteritems(host_details): 228 print(' %s: %s' % (k, '\n '.join(v.split(';')))) 229 else: 230 print(json.dumps(dict((key, (value.split(';') if ';' in value else value)) for key, value in host_details.items()))) 231 sys.exit(0) 232 233else: 234 235 parser.print_help() 236 sys.exit(1) 237