### # 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, )