#!/usr/local/bin/python # # (c) 2011-2020 Dennis Kaarsemaker # see COPYING for license details import hpilo import hpilo_fw import getpass import optparse import os from pprint import pprint import ssl import sys PY3 = sys.version_info[0] >= 3 if PY3: import configparser as ConfigParser from urllib.error import HTTPError basestring = str else: import ConfigParser from urllib2 import HTTPError input = raw_input ilo_methods = sorted([x for x in dir(hpilo.Ilo) if not x.startswith('_') and x.islower()]) def main(): usage = """ %prog [options] hostname method [args...] [ + method [args...]...] %prog download_rib_firmware ilotype version [version...] """ p = optparse.OptionParser(usage=usage, add_help_option=False) p.add_option("-l", "--login", dest="login", default=None, help="Username to access the iLO") p.add_option("-p", "--password", dest="password", default=None, help="Password to access the iLO") p.add_option("-i", "--interactive", action="store_true", default=False, help="Prompt for username and/or password if they are not specified.") p.add_option("-c", "--config", dest="config", default="~/.ilo.conf", help="File containing authentication and config details", metavar="FILE") p.add_option("-t", "--timeout", dest="timeout", type="int", default=60, help="Timeout for iLO connections") p.add_option("-j", "--json", dest="format", action="store_const", const="json", default="python", help="Output a json document instead of a python dict") p.add_option("-y", "--yaml", dest="format", action="store_const", const="yaml", default="python", help="Output a yaml document instead of a python dict") p.add_option("-P", "--protocol", dest="protocol", choices=("http","raw","local"), default=None, help="Use the specified protocol instead of autodetecting") p.add_option("-d", "--debug", dest="debug", action="count", default=0, help="Output debug information, repeat to see all XML data") p.add_option("-o", "--port", dest="port", type="int", default=443, help="SSL port to connect to") p.add_option('--ssl-verify', dest="ssl_verify", action="store_true", default=False, help="Verify SSL certificates against the trusted CA's") p.add_option('--ssl-ca-file', dest="ssl_ca_file", default=None, help="CA bundle to validate iLO certificate against, instead of the system CA's") p.add_option('--ssl-ignore-hostname', dest="ssl_ignore_hostname", action='store_true', default=False, help="Don't check if the hostname matches the certificate when verifying SSL certificates") p.add_option("-h", "--help", action="callback", callback=hpilo_help, help="show this help message or help for a method") p.add_option("-H", "--help-methods", action="callback", callback=hpilo_help_methods, help="show all supported methods") p.add_option('--save-response', dest="save_response", default=None, metavar='FILE', help="Store XML output in this file") p.add_option('--read-response', dest="read_response", default=None, metavar='FILE', help="Read XML response from this file instead of the iLO") p.add_option('-v', '--version', action="callback", callback=hpilo_version) opts, args = p.parse_args() if opts.format == 'json': import json elif opts.format == 'yaml': import yaml # Did we get correct arguments? if len(args) < 2: p.error("Not enough arguments") if args[1] not in ilo_methods and args[0] != 'download_rib_firmware': p.error("Unknown method: %s" % args[1]) config = ConfigParser.ConfigParser() if os.path.exists(os.path.expanduser(opts.config)): config.read(os.path.expanduser(opts.config)) if args[0] == 'download_rib_firmware': if config.has_option('firmware', 'mirror'): hpilo_fw.config(mirror=config.get('firmware', 'mirror')) return download(args[1], args[2:]) hostname = args.pop(0) args_ = list_split(args, '+') calls = [] for args in args_: method = args.pop(0) # Arguments must be passed as param=value pairs that are valid arguments to the methods if sys.version_info[0] >= 3: func = getattr(hpilo.Ilo, method)#.__func__ argnames = func.__code__.co_varnames[1:func.__code__.co_argcount] args_with_defaults = [] if func.__defaults__: args_with_defaults = argnames[-len(func.__defaults__):] else: func = getattr(hpilo.Ilo, method).im_func argnames = func.func_code.co_varnames[1:func.func_code.co_argcount] args_with_defaults = [] if func.func_defaults: args_with_defaults = argnames[-len(func.func_defaults):] params = {} for arg in args: if '=' not in arg: hpilo_help(None, None, method, None, 2) param, val = arg.split('=', 1) # Do we expect structured data? keys = param.split('.') if (len(keys) > 1) != (keys[0] in getattr(func, 'requires_dict', {})): hpilo_help(None, None, method, None, 2) if keys[0] not in argnames: hpilo_help(None, None, method, None, 2) # Optionally extract values from the config if val.startswith('$') and '.' in val: section, option = val[1:].split('.', 1) if config.has_option(section, option): val = config.get(section, option) # Do some type coercion for shell goodness if val.isdigit(): val = int(val) else: val = {'true': True, 'false': False}.get(val.lower(), val) params_ = params for key in keys[:-1]: if key not in params_: params_[key] = {} params_ = params_[key] params_[keys[-1]] = val for name in argnames: if name not in params and name not in args_with_defaults: hpilo_help(None, None, method, None, 2) calls.append((method, params)) # Do we have login information login = None password = None needs_login = bool(opts.read_response) for m, _ in calls: if m != 'xmldata': needs_login = True break if hostname == 'localhost': opts.protocol = 'local' needs_login = False if needs_login: if config.has_option('ilo', 'login'): login = config.get('ilo', 'login') if config.has_option('ilo', 'password'): password = config.get('ilo', 'password') if opts.login: login = opts.login if opts.password: password = opts.password if not login or not password: if opts.interactive: while not login: login = input('Login for iLO at %s: ' % hostname) while not password: password = getpass.getpass('Password for %s@%s:' % (login, hostname)) else: p.error("No login details provided") opts.protocol = { 'http': hpilo.ILO_HTTP, 'raw': hpilo.ILO_RAW, 'local': hpilo.ILO_LOCAL, }.get(opts.protocol, None) ssl_context = None if opts.ssl_verify: if sys.version_info < (2,7,9): p.error("SSL verification is only supported in python 2.7.9 or newer") ssl_context = ssl.create_default_context(cafile=opts.ssl_ca_file) ssl_context.options &= ~ssl.OP_NO_SSLv3 ssl_context.check_hostname = not opts.ssl_ignore_hostname # import debugme ilo = hpilo.Ilo(hostname, login, password, opts.timeout, opts.port, opts.protocol, len(calls) > 1, opts.ssl_verify, ssl_context) ilo.debug = opts.debug ilo.save_response = opts.save_response ilo.read_response = opts.read_response if config.has_option('ilo', 'hponcfg'): ilo.hponcfg = config.get('ilo', 'hponcfg') if config.has_option('firmware', 'mirror'): ilo.firmware_mirror = config.get('firmware', 'mirror') def _q(val): if isinstance(val, basestring): return '"%s"' % val.replace("\\","\\\\").replace('"','\\"') else: return str(val) for method, params in calls: if method == 'update_rib_firmware': params['progress'] = print_progress results = [getattr(ilo, method)(**params)] if 'progress' in params: params.pop('progress')("") if len(calls) > 1: results = ilo.call_delayed() if opts.format == 'json': if len(calls) == 1: results = results[0] json.dump(results, sys.stdout) elif opts.format == 'yaml': yaml.dump(results, sys.stdout) else: for method, params in calls: param_str = ', '.join(["%s=%s" % (x[0], _q(x[1])) for x in params.items()]) if method.startswith('get') or method == 'certificate_signing_request' or len(calls) == 1: result = results.pop(0) else: result = None if isinstance(result, basestring): print(">>> print(my_ilo.%s(%s))" % (method, param_str)) print(result) elif result is None: print(">>> my_ilo.%s(%s)" % (method, param_str)) else: print(">>> pprint(my_ilo.%s(%s))" % (method, param_str)) pprint(result) def hpilo_help(option, opt_str, value, parser, exitcode=0): if not value: if parser and parser.rargs and parser.rargs[0][0] != '-': value = parser.rargs[0] del parser.rargs[0] if not value: parser.print_help() else: if value in ilo_methods: import re, textwrap func = getattr(hpilo.Ilo, value).im_func code = func.func_code args = '' if code.co_argcount > 1: args = code.co_varnames[:code.co_argcount] defaults = func.func_defaults or [] args = ["%s=%s" % (x, x.upper()) for x in args[:len(args)-len(defaults)]] + \ ["[%s=%s]" % (x,str(y)) for x, y in zip(args[len(args)-len(defaults):], defaults) if x != 'progress'] args = ' ' + ' '.join(args[1:]) print(textwrap.fill("Ilo.%s%s:" % (value, args), 80)) doc = getattr(hpilo.Ilo, value).__doc__ or "No documentation" doc = re.sub(r':[a-z]+:`(.*?)`', r'\1', doc) if 'API note' in doc: doc = doc[:doc.find('API note')].strip() doc = re.sub('\s+', ' ', doc) print(textwrap.fill(doc, 80)) else: print("No such method: %s" % value) if parser: parser.exit(exitcode) else: sys.exit(exitcode) def hpilo_help_methods(option, opt_str, value, parser): print("""Supported methods: - %s""" % "\n- ".join(ilo_methods)) parser.exit() def hpilo_version(option, opt_str, value, parser): print(hpilo.__version__) sys.exit() def print_progress(text): sys.stdout.write('\r\033[K' + text) sys.stdout.flush() def list_split(lst, sep): ret = [] while True: try: pos = lst.index(sep) except ValueError: ret.append(lst) break ret.append(lst[:pos]) lst = lst[pos+1:] return ret def download(ilotype, versions): ilotype = ilotype.lower() config = hpilo_fw.config() if ilotype == 'all': for ilotype in sorted(config.keys()): if (versions == ['all']) == (' ' in ilotype): _download(config, ilotype) else: if not versions: _download(config, ilotype) elif versions == ['all']: for key in sorted(config.keys()): if key.startswith(ilotype + ' '): _download(config, key) else: for version in versions: _download(config, '%s %s' % (ilotype, version)) def _download(config, key): if key not in config: sys.stderr.write("Unknown ilo/firmware version: %s\n" % key) sys.exit(1) try: if hpilo_fw.download(key, progress=print_progress): print("") else: print("%s firmware version %s was already downloaded" % (key.split()[0], config[key]['version'])) except HTTPError as exc: print("\n%s" % str(exc)) if __name__ == '__main__': main()