Python 3 XML xml.etree парсинг stdout bash cli команды - PullRequest
0 голосов
/ 22 января 2019

Приветствую, хорошие люди из Python.

Я пытаюсь создать скрипт на Python, который выполняет две вещи.Он возьмет файл input.xml с некоторыми командами (которые пользователи могут редактировать позже и добавят больше), а затем создаст другой файл output.xml с результатами команд, а затем запустит другую программу .py, которая вставляет выходные данные через APIв веб-приложение.Все эти команды являются командами BASH, такими как dmidecode, cat, hpconfg и аналогичными.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>

Мне нужно перебрать этот файл input.xml (который будет позже, возможно, больше с большим количеством команд), и я буду использовать библиотеку Paramiko ssh для удаленного подключения ксерверы и собирать стандартный вывод из команд и анализировать стандартный вывод в файл output.xml.

Это мой старый скрипт для сбора и печати стандартного вывода команд, но я думаю, что для этой более сложной задачи мне понадобится модуль подпроцесса.

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")

Любая помощь и подсказки будутценится.

!!!ОБНОВЛЕНИЕ !!!

Я решил использовать CSV для создания файла output.csv с некоторым идентификатором, который я могу использовать позже для вызова внешнего файла api.py для вставки данных с API в веб-приложение.

Код выглядит следующим образом, но все еще завершен и требует дополнительной работы.Любая помощь будет оценена:

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")

1 Ответ

0 голосов
/ 22 января 2019

Согласно комментариям, вы можете добавлять каждый line в пустой список, открывать CSV-файл в нужном формате в режиме w (запись) и записывать список в CSV.Это создаст CSV-файл с данными в sshdata, если это то, что вы ищете:

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

Предлагаемое решение XML :

СледующееРешение обеспечивает рабочий процесс, который принимает hp_input.xml для поставщика HP.Конечно, у вас может быть dell_input.xml, если поставщик Dell.

Я использовал библиотеки xmltodict и dicttoxml для преобразования ввода в dict, добавив ключ output для каждогоКоманда, обновите значение для этого ключа на основе выходных данных для команды, затем снова преобразуйте выходной dict в xml и запишите в файл hp_output.xml или dell_output.xml.Чтобы написать xml в красивой печати, я использовал lxml.Прежде чем продолжить, убедитесь, что вы установили необходимые библиотеки с pip.

Сначала верните stdout из __call__() в классе ssh:

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

ДалееСледующая функция примет входной 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

Следующая функция получает значение команды из вышеуказанного dict и отправляет его на выполнение в remote, получает вывод и обновляет dictполучено сверху.

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

Теперь, когда у нас есть последний dict из вышеуказанной функции, следующая функция снова преобразует его в 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

Вы можете поместить все функции в один класс и добавить обработку исключений по своему усмотрению.Мы можем вызвать функции следующим образом:

data = get_output_dict()
xml = get_output_xml(data)

Код был протестирован с предоставленным входным xml и фиктивным значением для вывода.Хотя я не тестировал paramiko, этот код должен дать вам базовый рабочий процесс для получения желаемого выходного XML-файла, и, если возникнут какие-либо ошибки, они могут быть отлажены.

...