⌨ Labor omnia vincit ☮

RPM and Python

Posted in python by anaumov on 19.07.2011

В процессе тестирования, особенно если проводится мониторинг всей системы, часто нужно получить информацию об изменениях в пакетах (распаковать, сравнить версии, создать git-репозиторий, сравнить изменения), а так же возможно составить на основании этого какую-то статистику. Каждая компания-разработчик имеет свой арсенал подобного ПО. Как правило это небольшие скрипты, заточенные под конкретные задачи.
Создание подобного ПО дело совсем не хитрое. Давайте рассмотрим случай работы с RPM. В качестве языка будем использовать python.

Для работы с RPM в Python есть набор API, который находится в пакете rpm-python. RPM Python API предоставляет высокоуровневый интерфейс к функциональности RPM. Используя его мы можем не только получить доступ к базе данных RPM, но и работать с еще не установленными RPM-пакетами.

#!/usr/bin/env python
import rpm, sys
ts = rpm.TransactionSet()
file = ts.dbMatch('name', sys.argv[1])

for h in file:
     print "%s-%s-%s.%s" % (h['name'], h['version'], h['release'], h['arch'])

Здесь мы подключаем модуль rpm и получаем доступ к базе данных RPM через так называемый “сет транзакций”. Используя последний, мы можем инициализировать или перестроить существующую базу данных, а так же так же подключится к внешней (подключение к ней осуществляется через addMacro, а отключение через delMacro соответсвенно). Для получения информации о пакете используется шаблонный итератор dbMatch уже существующего сета транзакции. Он будет искать для нас информацию по заданому критерию. Как вы видете из примера, поиск будет осуществляться по имени(name), которое мы передадим программе как параметр(sys.argv[1]).

Запускаем код и сравниваем результат, например, с командой rpm -qa vim:

> rpm -qa vim
vim-7.3-5.3.x86_64
> ./my_rpm vim
vim-7.3-5.3.x86_64

Выборка в данном примере осуществляется по имени. Если оставить dbMatch() пустым, то вывод будет аналогичен выводу команды rpm -qa, т.е. будет считана вся база пакетов. Так, например, можно узнать сколько пакетов уcтановленно в системе: ./my_rpm | wc -l

Мы считываем имя, версию, релиз и арихитектуру пакета (формат я подогнал под вывод программы rpm специально). Это далеко не единственные свойства пакета. Все они перечисленны как константы и определены в модуле rpm. Обращаться к ним можно как к структуре тегов. Я не нашел полного списка для Python, но он одинаков для C, Perl и Python. Пример для Perl можно посмотреть вот тут (RPMTAG_*).
На практике получается что-то типа этого:

#!/usr/bin/env python
import rpm, sys

ts = rpm.TransactionSet()
mi = ts.dbMatch('name', sys.argv[1])

for hdr in mi:
        print "NAME...........", hdr[rpm.RPMTAG_NAME]
        print "VERSION........", hdr[rpm.RPMTAG_VERSION]
        print "RELEASE........", hdr[rpm.RPMTAG_RELEASE]
        print "ARCH...........", hdr[rpm.RPMTAG_ARCH]
        print "LICENSE........", hdr[rpm.RPMTAG_LICENSE]
        print "GROUP..........", hdr[rpm.RPMTAG_GROUP]
        print "DISTRO.........", hdr[rpm.RPMTAG_DISTRIBUTION]
        print "PACKAGER.......", hdr[rpm.RPMTAG_PACKAGER]
        print "PROJECT_URL....", hdr[rpm.RPMTAG_URL]
        print "\nREQUIREMENTS:"
        for i in hdr[rpm.RPMTAG_REQUIRENAME]:
                print "...............",i

        print "\n",hdr[rpm.RPMTAG_DESCRIPTION],"\n"

На выводе ничего нового, почти тот же rpm -qi _name_:

>./my_rpm bash
NAME........... bash
VERSION........ 4.1
RELEASE........ 20.25.1
ARCH........... x86_64
LICENSE........ GPLv2+
GROUP.......... System/Shells
DISTRO......... openSUSE 11.4
PACKAGER....... http://bugs.opensuse.org
PROJECT_URL.... http://www.gnu.org/software/bash/bash.html

REQUIREMENTS:
............... rpmlib(PayloadFilesHavePrefix)
............... rpmlib(CompressedFileNames)
............... /bin/sh
............... libc.so.6()(64bit)
............... libc.so.6(GLIBC_2.11)(64bit)
............... libc.so.6(GLIBC_2.2.5)(64bit)
............... libc.so.6(GLIBC_2.3)(64bit)
............... libc.so.6(GLIBC_2.3.4)(64bit)
............... libc.so.6(GLIBC_2.4)(64bit)
............... libdl.so.2()(64bit)
............... libdl.so.2(GLIBC_2.2.5)(64bit)
............... libncurses.so.5()(64bit)
............... libreadline.so.6()(64bit)
............... rpmlib(PayloadIsLzma)

Bash is an sh-compatible command interpreter that executes commands
read from standard input or from a file.  Bash incorporates useful
features from the Korn and C shells (ksh and csh).  Bash is intended to
be a conformant implementation of the IEEE Posix Shell and Tools
specification (IEEE Working Group 1003.2).

Authors:
--------
    Brian Fox 
    Chet Ramey

Почти ничего нового за тем лишь исключением, что собирать информацию мы можем разом со всех пакетах и не только тех, которые в ней установленны.

Следующий код ищет *.src.rpm пакеты в текущей директори, распаковывет их и создает для каждого git-репозиторий:

#!/usr/bin/env python

import os, rpm, sys, re, subprocess as sp

def make(i,fdno,hdr, ts):
        os.chdir(cmd)
        name = hdr[rpm.RPMTAG_NAME]

        try:
                os.chdir(name)                           # change to ../name
        except OSError:
                os.system("mkdir "+name)                 # create ../name
                os.chdir(name)                           # change current directory

        try:
                os.chdir(hdr['version'])                 # change to ../name/version)
        except OSError:
                os.system("mkdir "+hdr['version'])       # create ../name/version
                os.chdir(hdr['version'])

        #--- unpack SRC.RPM --------------------------------------------------------------

        os.system("pwd")
        source = "../../" + i                            # source = ../../name-1.1.1-1.src.rpm

        unrpm = "/usr/bin/unrpm "+source                 # not install, just unpack
        os.system(unrpm)

        cmd_p__ = sp.Popen("pwd", stdout=sp.PIPE)        # get address
        cmd_p_ = cmd_p__.communicate()[0]                # of the current direcrory
        cmd_p = cmd_p_[:-1]                              # ... and remove '\n' (:
        print cmd_p

        list = os.listdir(cmd_p)
        patch_rpm = filter(lambda x: x.endswith('.patch'), list);  # l | grep .patch
        print patch_rpm

        #--- git add and commit ----------------------------------------------------------

        os.system("git add *")
        commit = "git commit -a -m " +name
        os.system(commit)

        os.chdir(cmd)                                    # come back

                                                         # main()
ts = rpm.TransactionSet()                                # get access to the rpm database
ts.setVSFlags(~(rpm.RPMVSF_NEEDPAYLOAD))

cmd__ = sp.Popen("pwd", stdout=sp.PIPE)                  # get address
cmd_ = cmd__.communicate()[0]                            # of the current direcrory
cmd = cmd_[:-1]                                          # ... and remove '\n' of course (:

files = os.listdir(cmd)
src_rpm = filter(lambda x: x.endswith('.src.rpm'), files);
print src_rpm                                            # l | grep .src.rpm

fdno = os.open(src_rpm[1], os.O_RDONLY)                  # get info about rpm
hdr = ts.hdrFromFdno(fdno)                               # create struct which rpm info
os.close(fdno)

os.system("git init")                                    # create git repo
for i in src_rpm:
        fdno = os.open(i, os.O_RDONLY)
        hdr = ts.hdrFromFdno(fdno)
        os.close(fdno)

        make(i,fdno,hdr, ts)

Тут мы работатем с внешними файлами, так что используем open из модуля os для открытия файла, затем считываем информацию в структуру hdr, как и в прошлых примерах, и закрываем дескриптор файла. Точно так же мы можем обращаться к этой структуре для получения информации обо всех перечисленных выше свойствах: лицензия, зависимости и т.д. Не забывайте, что информации этой нет в базе данных, т.к. пакет не установлен, а просто распакован (27 строчка).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: