#!/usr/bin/python
#
# (c) 2011-2020 Dennis Kaarsemaker <dennis@kaarsemaker.net>
# 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()