简体   繁体   English

Python 3 XML xml.etree解析标准输出bash cli命令

[英]Python 3 XML xml.etree parsing stdout bash cli commands

Greetings good Python people. 问候好Python的人。

I am trying to build a python script that is doing two things. 我正在尝试构建一个做两件事的python脚本。 It will take an input.xml file with some commands (that the users can edit later and add more) and then create another output.xml file with the results of the commands and then start some other .py program that inserts the output via API to a web application. 它将使用带有一些命令的input.xml文件(用户可以稍后编辑并添加更多命令),然后使用命令的结果创建另一个output.xml文件,然后启动其他一些.py程序,以通过API插入输出到Web应用程序。 These commands are all BASH commands, like dmidecode, cat, hpconfg and similar. 这些命令都是BASH命令,例如dmidecode,cat,hpconfg等。 The Input.xml looks like this: Input.xml看起来像这样:

    <?xml version="1.0"?>
<platform>
  <vendor name="HP">
     <tests type = "Hardware" name="bios_versions">
        <command>dmidecode --type bios | grep -e Version -e "Release Date"</command>
     </tests>
     <tests type = "Hardware" name="iLo">
        <command>hponcfg -f /tmp/Get_ILO_Firmware_Version.xml | awk -F"\"" '/LICENSE_TYPE/ {print $2}'</command>
     </tests>
     <tests type = "Hardware" name="hard_disks">
        <command>hpssacli ctrl slot=0 ld all show detail | egrep "Status|Size"</command>
        <command>hpssacli ctrl slot=1 ld all show detail | egrep "Status|Size"</command>
        <command>hpssacli ctrl slot=2 ld all show detail | egrep "Status|Size"</command>
        <command>hpssacli ctrl slot=3 ld all show detail | egrep "Status|Size"</command>
     </tests>

  </vendor>
</platform>

I would need to iterate trough this input.xml (that will be later maybe bigger with more commands) and i will use the Paramiko ssh library to connect remotely to the servers and collect the stdout from the commands and parse the stdout to an output.xml file. 我将需要遍历这个input.xml(以后可能会通过更多命令变得更大),并且我将使用Paramiko ssh库远程连接到服务器,并从命令中收集stdout并将stdout解析为输出。 xml文件。

This is my older script to collect and print the stdout of the commands but I think I will need a subprocess module for this more complex task. 这是我收集和打印命令标准输出的较早脚本,但是我认为我将需要一个子流程模块来完成此更复杂的任务。

import atexit
import paramiko
import sys

if len(sys.argv) < 2:
    print("args missing")
    sys.exit(1)

hostname = sys.argv[1]
password = sys.argv[2]
#user = sys.argv[3]
username = "root"

#Check Vendor
client = paramiko.SSHClient()

client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port = 22, username=username, password=password)
stdin, stdout, stderr = client.exec_command("dmidecode | egrep Vendor | awk '{print $2}'")
vendor = stdout.read().decode('utf-8')
vendor = vendor.strip('\n') 
client.close()
#finish check vendor

class myssh:

    def __init__(self, hostname, username, password, port = 22):
        client = paramiko.SSHClient()
        client.load_system_host_keys()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(hostname, port=port, username=username, password=password)
        atexit.register(client.close)
        self.client = client

    def __call__(self, command):
        stdin,stdout,stderr = self.client.exec_command(command)
        sshdata = stdout.readlines()
        for line in sshdata:
            #end = '' -  to  loose space after the loops new lines
            print(line,end = '')

remote = myssh(hostname,username,password)

#CHECK HARDWARE COMMANDS
if vendor == "Dell":
   print("1. HARDWARE VENDOR:"+vendor)
   print("2. CPU INFO")
   remote("cat /proc/cpuinfo | grep -e 'model name' -e 'physical id' | cut -c 1- | sort | uniq -c")
   remote("cat /etc/release | grep PLATFORM")
   print("3. PHYSICAL DISK SMART PREDICABLE STATUS: NO is Ok!")
   remote("omreport storage pdisk controller=0 | egrep '^ID|Status|State|Failure|Capacity' | grep -v Power | grep -v 'Status' | grep 'Failure Predicted'")
   print("4. BATERRY STATUS:")
   remote("omreport storage battery | grep H740 -A 4  | grep -oh 'Ok'")
   remote("omreport storage battery | grep H740 -A 4  | grep -oh 'Ready'")
   print("5. Bios Version:")
   remote("omreport chassis bios | grep 'Version' | awk '{print $3}'")

elif vendor == "HP":
   print("1. HARDWARE VENDOR:"+vendor)
   print("2. CPU INFO")
   remote("cat /proc/cpuinfo | grep -e 'model name' -e 'physical id' | cut -c 1- | sort | uniq -c")
   remote("cat /etc/release | grep PLATFORM")
   print("3. PHYSICAL DISK STATUS")
   remote("hpssacli ctrl all show config | egrep -i '(ok|failed|error|offline|rebuild|ignoring|degraded|skipping|nok)'")
   print("4. BATERRY STATUS")
   remote("hpssacli ctrl all show status | grep 'OK'")
   print("5. Bios Version:")
   remote("dmidecode --type bios | grep -e Version -e 'Release Date'")

else:
   print("1. HARDWARE VENDOR:"+vendor)
   print(vendor+" is this")

Any help and hints would be appreciated. 任何帮助和提示,将不胜感激。

!!! UPDATE !!! 更新!!!

I decided to use CSV to create output.csv file with some ID which I can use later to call upon external api.py file to insert data with API to a Web Application. 我决定使用CSV创建具有某些ID的output.csv文件,以后可以使用它调用外部api.py文件,以将具有API的数据插入Web应用程序。

The code looks like this, but still now finished and needs more work. 代码看起来像这样,但是现在仍然完成并且需要更多的工作。 Any help would be appreciated: 任何帮助,将不胜感激:

class myssh:

    def __init__(self, hostname, username, password, port=22):
        client = paramiko.SSHClient()
        client.load_system_host_keys()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(hostname, port=port, username=username, password=password)
        atexit.register(client.close)
        self.client = client

    def __call__(self, command):
        stdin, stdout, stderr = self.client.exec_command(command)
        sshdata = stdout.readlines()
        for line in sshdata:
            # end = '' -  to  loose space after the loops new lines
            print(line, end='')


remote = myssh(hostname, username, password)


if vendor == "Dell":
    f = open('dellcsv.csv', 'r')
    csv_f = csv.reader(f, delimiter=';')
    for row in csv_f:
        remote(row[4])
        # print(row[4])
    f.close()

elif vendor == "HP":
    f = open('dellcsv.csv', 'r')
    csv_f = csv.reader(f, delimiter=';')
    for row in csv_f:
        remote(row[4])
        # print(row[4])
    f.close()

else:
    print("1. HARDWARE VENDOR:" + vendor)
    print(vendor + " is this")

As per comments, you can append every line to an empty list, open the csv file in desired format in w (write) mode and write the list to the csv. 根据注释,您可以将每line追加到一个空列表中,以w (写入)模式以所需格式打开csv文件,然后将该列表写入csv。 This will create the csv file with the data in sshdata , if that's what you are looking for: 如果您要查找的是,这将使用sshdata的数据创建csv文件:

import csv
filename = 'ssh_output.csv'
def __call__():
    #your code here#
    hostdata_list = []
    hardware_data = []
    for line in sshdata:
        if 'HARDWARE' in line:
            row = 'some_data'
            hardware_data.append(row)
        hostdata_list.append(line)

    with open('input.csv', 'r') as f:
        reader = csv.reader(f)
        header = next(reader)

    with open(filename, 'a+') as f:
        writer = csv.writer(f)  
        writer.writerow(header)  
        if hardware_data is not None:
            writer.writerow(hardware_data)
        writer.writerow(hostdata_list)
    print('sshdata written to {} for host {}'.format(filename, hostname))

A proposed XML solution : 提出的XML解决方案

The following solution provides a workflow that accepts hp_input.xml for vendor HP. 以下解决方案提供了接受供应商HP的hp_input.xml的工作流。 Of course, you may have dell_input.xml if the vendor is Dell. 当然,如果供应商是Dell,则可能会有dell_input.xml

I've used the xmltodict and dicttoxml libraries to convert the input to a dict, add an output key for each command, update the value for this key based on output for the command, then convert the output dict to xml again and write to an hp_output.xml or dell_output.xml file. 我已经使用xmltodictdicttoxml库将输入转换为dict,为每个命令添加一个output键,根据命令的output更新此键的值,然后再次将输出dict转换为xml并写入hp_output.xmldell_output.xml文件。 To write the xml in pretty print, I used lxml . 为了漂亮地打印xml,我使用了lxml Make sure you install the required libraries with pip before proceeding. 在继续之前,请确保使用pip安装所需的库。

First, return the stdout from __call__() in the ssh class: 首先,从ssh类的__call__()返回stdout

class ssh:
    #your code#
    def __call__():
        stdout_outputs = []
        #your code#
        stdout_outputs.append(stdout.read())
        return stdout_outputs

Next, the following function will accept the input xml and convert it to dict: 接下来,以下函数将接受输入的xml并将其转换为dict:

import xmltodict
import lxml.etree as etree
import dicttoxml
import json

def xml_to_dict(xml_doc):
    with open(xml_doc) as f:
        data = xmltodict.parse(f.read())
        # print(json.dumps(data, indent=4)) #check dict
    return data

The following function gets the command value from the above dict and sends it for execution to remote , receives the output and updates the dict received from above. 以下函数从上面的字典中获取命令值,并将其发送给remote ,以接收输出并更新从上面接收的字典。

def get_output_dict():    
    if vendor == 'HP':    
        data = xml_to_dict(xml_doc='hp_input.xml')      
    elif vendor == 'Dell':
        data = xml_to_dict(xml_doc='dell_input.xml')          
    for test in data['platform']['vendor']['tests']:        
        command = test.get('command',[]) #continue if command is not present
        if isinstance(command, list):
            for i in command:
                output = list(dict(i=remote(i)))                
        output = list(dict(command=remote(command)))              
        test['output'] = output
    return json.loads(json.dumps(data))

Now that we have the final dict from the above function, the following function will convert it to xml again and write it to an output xml file: 现在我们有了上述函数的最终决定,下面的函数将再次将其转换为xml并将其写入输出xml文件:

def get_output_xml(output_dict):
    output_xml = dicttoxml.dicttoxml(output_dict,custom_root='output', attr_type=False, root=False)
    if vendor == 'HP':
        filename = 'hp_output.xml'
    elif vendor == 'Dell':
        filename = 'dell_output.xml'
    tree = etree.fromstring(output_xml)
    output_xml_string = etree.tostring(tree, pretty_print=True)        
    with open(filename, 'wb') as f:
        f.write(output_xml_string)
    print('Output for hostname server: {} written to: {}'.format(hostname, filename))        
    return output_xml

You may put all the functions into one class and add exception handling as you like. 您可以将所有函数放在一个类中,并根据需要添加异常处理。 We can call the functions like this: 我们可以这样调用函数:

data = get_output_dict()
xml = get_output_xml(data)

The code was tested with input xml provided and a dummy value for output. 使用提供的输入xml和用于输出的虚拟值对代码进行了测试。 Although, I haven't tested with paramiko , this code should give you a basic workflow to get your desired output xml and if any errors crop up, they can be debugged. 尽管我尚未使用paramiko进行测试, paramiko此代码应为您提供基本的工作流程,以获取所需的输出xml,并且如果出现任何错误,可以对其进行调试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM