Files

425 lines
16 KiB
Python

###
# Copyright 2016-2021 Hewlett Packard Enterprise, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###
# -*- coding: utf-8 -*-
"""Save Command for RDMC"""
import json
from collections import OrderedDict
import redfish.ris
try:
from rdmc_helper import (
Encryption,
InvalidCommandLineError,
InvalidCommandLineErrorOPTS,
InvalidFileFormattingError,
ReturnCodes,
iLORisCorruptionError,
)
except ImportError:
from ilorest.rdmc_helper import (
Encryption,
InvalidCommandLineError,
InvalidCommandLineErrorOPTS,
InvalidFileFormattingError,
ReturnCodes,
iLORisCorruptionError,
)
# default file name
__filename__ = "ilorest.json"
class SaveCommand:
"""Constructor"""
def __init__(self):
self.ident = {
"name": "save",
"usage": None,
"description": "Run to save a selected type to a file"
"\n\texample: save --selector HpBios.\n\n\tChange the default "
"output filename\n\texample: save --selector HpBios. -f "
"output.json\n\n\tTo save multiple types in one file\n\texample: "
"save --multisave Bios.,ComputerSystem.",
"summary": "Saves the selected type's settings to a file.",
"aliases": [],
"auxcommands": ["SelectCommand"],
}
self.filename = __filename__
self.cmdbase = None
self.rdmc = None
self.auxcommands = dict()
def run(self, line, help_disp=False):
"""Main save worker function
:param line: command line input
:type line: string.
"""
if help_disp:
self.parser.print_help()
return ReturnCodes.SUCCESS
try:
(options, args) = self.rdmc.rdmc_parse_arglist(self, line)
except (InvalidCommandLineErrorOPTS, SystemExit):
if ("-h" in line) or ("--help" in line):
return ReturnCodes.SUCCESS
else:
raise InvalidCommandLineErrorOPTS("")
if options.multisave:
options.selector = ""
if (
hasattr(options, "selector")
and options.selector is not None
and "hpbaseconfigs" not in options.selector.lower()
and "hpebaseconfigs" not in options.selector.lower()
and "bios" not in options.selector.lower()
):
self.savevalidation(options)
else:
# filename validations and checks
self.cmdbase.login_validation(self, options)
self.filename = None
if options.filename and len(options.filename) > 1:
raise InvalidCommandLineError("Save command doesn't support multiple filenames.")
elif options.filename:
self.filename = options.filename[0]
elif self.rdmc.config:
if self.rdmc.config.defaultsavefilename:
self.filename = self.rdmc.config.defaultsavefilename
if not self.filename:
self.filename = __filename__
# if args:
# raise InvalidCommandLineError("Save command takes no arguments.")
self.rdmc.ui.printer("Saving configuration...\n")
if options.filter:
try:
if (str(options.filter)[0] == str(options.filter)[-1]) and str(options.filter).startswith(("'", '"')):
options.filter = options.filter[1:-1]
(sel, val) = options.filter.split("=")
sel = sel.strip()
val = val.strip()
except:
raise InvalidCommandLineError("Invalid filter" " parameter format [filter_attribute]=[filter_value]")
instances = self.rdmc.app.select(
selector=self.rdmc.app.selector,
fltrvals=(sel, val),
path_refresh=True,
)
contents = self.saveworkerfunction(options, instances=instances)
else:
contents = self.saveworkerfunction(options)
if options.multisave:
for select in options.multisave:
try:
self.auxcommands["select"].run(select)
except:
pass
contents += self.saveworkerfunction(options)
if not contents:
raise redfish.ris.NothingSelectedError
else:
# TODO: Maybe add this to the command. Not sure we use it elsewhere in the lib
contents = self.add_save_file_header(contents)
if options.encryption:
with open(self.filename, "wb") as outfile:
outfile.write(
Encryption().encrypt_file(
json.dumps(contents, indent=2, cls=redfish.ris.JSONEncoder),
options.encryption,
)
)
else:
with open(self.filename, "w") as outfile:
outfile.write(json.dumps(contents, indent=2, cls=redfish.ris.JSONEncoder, sort_keys=True))
self.rdmc.ui.printer("Configuration saved to: %s\n" % self.filename)
self.cmdbase.logout_routine(self, options)
# Return code
return ReturnCodes.SUCCESS
def saveworkerfunction(self, options, instances=None):
"""Returns the currently selected type for saving
:param instances: list of instances from select to save
:type instances: list.
"""
try:
if (
options.selector is not None
and (
"bios" in options.selector.lower()
or "hpbaseconfigs" in options.selector.lower()
or "hpebaseconfigs" in options.selector.lower()
)
) and (options.multisave is None or options.multisave == ""):
raise KeyError
content = self.rdmc.app.getprops(insts=instances)
try:
contents = [{val[self.rdmc.app.typepath.defs.hrefstring]: val} for val in content]
except KeyError:
try:
contents = [{val["links"]["self"][self.rdmc.app.typepath.defs.hrefstring]: val} for val in content]
except KeyError:
raise iLORisCorruptionError(
"iLO Database seems to be corrupted. Please check. Reboot the server to " "restore\n"
)
except Exception:
config_path = None
contents = list()
if options.selector is not None:
if options.selector.lower() == "bios." or "bios.v" in options.selector.lower():
config_path = ["/redfish/v1/systems/1/bios/settings/"]
elif options.selector.lower() == "bios":
config_path = [
"/redfish/v1/systems/1/bios/settings/",
"/redfish/v1/systems/1/bios/mappings/",
"/redfish/v1/systems/1/bios/oem/hpe/mappings/",
]
elif "hpbaseconfigs" in options.selector.lower() or "hpebaseconfigs" in options.selector.lower():
config_path = [
"/redfish/v1/systems/1/bios/oem/hpe/baseconfigs/",
"/redfish/v1/systems/1/bios/oem/hpe/nvmeof/baseconfigs/",
"/redfish/v1/systems/1/bios/oem/hpe/iscsi/baseconfigs/",
"/redfish/v1/systems/1/bios/oem/hpe/tlsconfig/baseconfigs/",
"/redfish/v1/systems/1/bios/oem/hpe/serverconfiglock/baseconfigs/",
"/redfish/v1/systems/1/bios/oem/hpe/kmsconfig/baseconfigs/",
"/redfish/v1/systems/1/bios/oem/hpe/boot/baseconfigs/",
"/redfish/v1/systems/1/bios/baseconfigs/",
"/redfish/v1/systems/1/bios/nvmeof/baseconfigs/",
"/redfish/v1/systems/1/bios/iscsi/baseconfigs/",
"/redfish/v1/systems/1/bios/tlsconfig/baseconfigs/",
"/redfish/v1/systems/1/bios/serverconfiglock/baseconfigs/",
"/redfish/v1/systems/1/bios/kmsconfig/baseconfigs/",
"/redfish/v1/systems/1/bios/boot/baseconfigs/",
]
elif "hpebiosmapping" in options.selector.lower():
config_path = [
"/redfish/v1/systems/1/bios/mappings",
"/redfish/v1/systems/1/bios/oem/hpe/mappings/",
]
result = None
for b in config_path:
try:
result = self.rdmc.app.get_handler(b, silent=True, service=True)
except:
pass
if result is not None:
if result.status == 200:
d = {b: result.dict}
contents.append(d)
type_string = self.rdmc.app.typepath.defs.typestring
templist = list()
srnum_list = list()
for content in contents:
typeselector = None
pathselector = None
for path, values in content.items():
# if "Managers/1/EthernetInterfaces/1" not in path:
for dictentry in list(values.keys()):
if dictentry == type_string:
typeselector = values[dictentry]
pathselector = path
del values[dictentry]
if dictentry in [
"IPv4Addresses",
"IPv6Addresses",
"IPv6AddressPolicyTable",
"MACAddress",
"StaticNameServers",
# "AutoNeg",
# "FullDuplex",
"SpeedMbps",
]:
del values[dictentry]
if dictentry in [
"IPv6StaticAddresses",
"IPv6StaticDefaultGateways",
"IPv4StaticAddresses",
]:
if values[dictentry]:
del values[dictentry]
if dictentry in ["IPv4", "IPv6", "DHCPv6", "DHCPv4"]:
if "DNSServers" in values[dictentry]:
del values[dictentry]["DNSServers"]
del values["Oem"]["Hpe"][dictentry]["DNSServers"]
if "UseDNSServers" in values[dictentry]:
del values[dictentry]["UseDNSServers"]
del values["Oem"]["Hpe"][dictentry]["UseDNSServers"]
if values:
skip = False
if "SerialNumber" in values and values["SerialNumber"] is not None and values["SerialNumber"] != "":
if values["SerialNumber"] in srnum_list:
skip = True
else:
srnum_list.append(values["SerialNumber"])
tempcontents = dict()
if not skip:
if typeselector and pathselector:
tempcontents[typeselector] = {pathselector: values}
else:
raise InvalidFileFormattingError("Missing path or selector in input file.")
templist.append(tempcontents)
return templist
def nested_sort(self, data):
"""Helper function to sort all dictionary key:value pairs
:param data: dictionary to sort
:type data: dict.
"""
for key, value in data.items():
if isinstance(value, dict):
data[key] = self.nested_sort(value)
data = OrderedDict(sorted(list(data.items()), key=lambda x: x[0]))
return data
def savevalidation(self, options):
"""Save method validation function
:param options: command line options
:type options: list.
"""
if options.multisave:
options.multisave = options.multisave.replace('"', "").replace("'", "")
options.multisave = options.multisave.replace(" ", "").split(",")
if not len(options.multisave) >= 1:
raise InvalidCommandLineError("Invalid number of types in multisave option.")
options.selector = options.multisave[0]
options.multisave = options.multisave[1:]
self.cmdbase.login_select_validation(self, options)
# filename validations and checks
self.filename = None
if options.filename and len(options.filename) > 1:
raise InvalidCommandLineError("Save command doesn't support multiple filenames.")
elif options.filename:
self.filename = options.filename[0]
elif self.rdmc.config:
if self.rdmc.config.defaultsavefilename:
self.filename = self.rdmc.config.defaultsavefilename
if not self.filename:
self.filename = __filename__
def add_save_file_header(self, contents):
"""Helper function to retrieve the comments for save file
:param contents: current save contents
:type contents: list.
"""
templist = list()
headers = self.rdmc.app.create_save_header()
templist.append(headers)
for content in contents:
templist.append(content)
return templist
def definearguments(self, customparser):
"""Wrapper function for new command main function
:param customparser: command line input
:type customparser: parser.
"""
if not customparser:
return
self.cmdbase.add_login_arguments_group(customparser)
customparser.add_argument(
"-f",
"--filename",
dest="filename",
help="Use this flag if you wish to use a different filename than the default one. "
"The default filename is %s." % __filename__,
action="append",
default=None,
)
customparser.add_argument(
"--selector",
dest="selector",
help="Optionally include this flag to select a type to run the current command on. "
"Use this flag when you wish to select a type without entering another command, "
"or if you wish to work with a type that is different from the one currently "
"selected.",
default=None,
# required=True,
)
customparser.add_argument(
"--multisave",
dest="multisave",
help="Optionally include this flag to save multiple types to a single file. "
"Overrides the currently selected type.\n\t Usage: --multisave type1.,type2.,type3.",
default="",
)
customparser.add_argument(
"--filter",
dest="filter",
help="Optionally set a filter value for a filter attribute. This uses the provided "
"filter for the currently selected type. Note: Use this flag to narrow down your "
"results. For example, selecting a common type might return multiple objects that "
"are all of that type. If you want to modify the properties of only one of those "
"objects, use the filter flag to narrow down results based on properties."
"\n\t Usage: --filter [ATTRIBUTE]=[VALUE]",
default=None,
)
customparser.add_argument(
"-j",
"--json",
dest="json",
action="store_true",
help="Optionally include this flag if you wish to change the displayed output to "
"JSON format. Preserving the JSON data structure makes the information easier to "
"parse.",
default=False,
)
customparser.add_argument(
"--encryption",
dest="encryption",
help="Optionally include this flag to encrypt/decrypt a file using the key provided.",
default=None,
)