Python на примере демона уведомления о новых коммитах Git

Работая в команде я люблю быть в курсе активности участников. Поэтому было решено написать демона наблюдающего за поступлением новых коммитов в репозиторий git’а. Так как я работаю в Ubuntu, то уведомление было реализовано встроенным способом — библиотекой libnotify.
Язык — Python!

image

В статье упоминается:
1. Демон на Python;
2. Логирование на Python;
3. Хранение конфигурационных файлов программ на Python;
4. Работа с командами ОС из скриптов Python;
5. Получения списка последних изменений из git’а;
6. Стандартные всплывающие уведомления Ubuntu.

Для реализации задачи был выбран язык Python (высокоуровневый, интерпретируемый, объектно-ориентированный и расширяемый язык программирования), так как я его не знаю.
Для начала мне очень помогли два источника:
— официальная документация: http://docs.python.org/tutorial/index.html;
— цикл статей IBM на русском языке: https://www.ibm.com/developerworks/ru/library/l-python_part_1/.

Во время изучения основ, приступаем к написанию программы.

1. Демон


В сети встречается много реализаций демонов, выбрал один из готовых с положительными отзывами и привлекательным названием: http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/. У этого демона были проблемы с завершением работы командой "daemon.py stop" вот в этом месте:
except OSError, err:
    err = str(err)
    if err.find("No such process") > 0:
        if os.path.exists(self.pidfile):
            os.remove(self.pidfile)

Это как видно возникло это из-за русской локали, фраза "No such process" в моей системе возвращалась на русском языке. Простой способ исправить — удалить эту проверку:
except OSError, err:
    if os.path.exists(self.pidfile):
        os.remove(self.pidfile)


2. Ведение логов


Простейшим средством оповещение о процессе работы программы является использование функции print(). Но эта программа будет запускаться в качестве демона и не предполагает вывод информации о своем состоянии в консоль запуска. Удобным вариантом в этом случае является запись лога в файл. В Python’е есть встроенный способ логирования входящий в стандартную библиотеку — модуль logging (http://docs.python.org/library/logging.html).
Модуль имеет множество вариантов ведения лога (handlers, http://docs.python.org/library/logging.handlers.html): StreamHandler, FileHandler, WatchedFileHandler, RotatingFileHandler, TimedRotatingFileHandler, SocketHandler, DatagramHandler, SysLogHandler, NTEventLogHandler, SMTPHandler, MemoryHandler, HTTPHandler. Для контроля работы демона использовался FileHandler:
logging.basicConfig(filename = tempfile.gettempdir() + '/gitPushNotify.log',
                    level = logging.DEBUG,
                    format = '%(asctime)s %(levelname)s: %(message)s',
                    datefmt = '%Y-%m-%d %I:%M:%S')
logging.info('Daemon start')


3. Хранение конфигурации программ на Python


Для хранения конфигурации приложений используется ini файл и встроенный модуль работы с ними ConfigParser (http://docs.python.org/library/configparser.html):
config = ConfigParser.ConfigParser()
configPath = os.path.dirname(__file__) + '/config.ini'
config.read(configPath)

Получение значения параметров функциями (в данном случае получение integer значения):
timeout = config.getint('daemon', 'timeout')


4. Работа с командам ОС из скриптов Python


Для выполнения системных команд используется метод check_output() модуля subprocess (http://docs.python.org/library/subprocess.html):
sourceOutput = subprocess.check_output('cd ' + repositoryPath, shell=True)

Также можно использовать методы модуля os:
sourceOutput = os.system(commandString)

Документация рекомендует использовать subprocess.

5. Получения списка последних изменений из git’а


Для просмотра последних изменений репозитория удобно использовать команду whatchanged (http://schacon.github.com/git/git-whatchanged.html). Эта команда позволяет задавать формат выводимых сообщений лога, устанавливать количество выводимых изменений. Пример использования команды:
$ git whatchanged master -10 --date=raw --date-order --pretty=format:"%H %n%cn %n%ce %n%ct %n%s"

Аргументы по порядку:
master — ветка репозитория;
-10 — количество выводимых записей;
--date-order — сортировка по дате изменения;
--pretty=format:"..." — формат вывода.

6. Стандартные всплывающие уведомления Ubuntu


Работа со всплывающими уведомлениями в Ubuntu осуществляется через библиотеку libnotify (https://wiki.ubuntu.com/NotificationDevelopmentGuidelines). Проверить установлена ли она можно командой:
$ dpkg -l | grep libnotify-bin

Либо сразу выполнить:
$ sudo apt-get install libnotify-bin

Отобразить уведомление можно используя команду:
$ notify-send "Habr!"
$ notify-send -i notification-message-email "Title" "Message"

Флаг -i — изображение, указывается системное название или путь к любому изображению в формате svg, png или jpg.

Запуск демона


Необходимо сделать файл исполняемым:
$ chmod +x gitPushNotifyDaemon.py

Перед запуском в конфигурационном файле необходимо указать путь к репозиторию и частоту опроса:
$ vim config.ini

Запуск:
$ python gitPushNotifyDaemon.py start

Если всё прошло успешно:
Daemon starting..

Появится стартовое уведомление:

image

Теперь демона можно увидеть в списке процессов, выполнив команду:
$ ps uax | grep gitPushNotifyDaemon.py

За процессом работы демона можно наблюдать используя лог:
$ tail -f /tmp/gitPushNotify.log

Также можно отдельно запускать файл gitPushNotify.py для отладки:
$ python gitPushNotify.py


Репозиторий проекта: https://github.com/antonfisher/gitPushNotify

На этом всё. Удачного дня!

Ссылки:
1. http://docs.python.org/tutorial/index.html — официальный туториал;
2. https://www.ibm.com/developerworks/ru/library/l-python_part_1/ — цикл статей IBM;
3. http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ — реализация демона от Sander Marechal;
4. http://docs.python.org/library/logging.htmlPython, модуль Logging;
5. http://docs.python.org/library/configparser.htmlPython, модуль ConfigParser;
6. http://docs.python.org/library/subprocess.htmlPython, модуль Subprocess;
7. https://wiki.ubuntu.com/NotificationDevelopmentGuidelines — описание libnotify;
8. http://schacon.github.com/git/git-whatchanged.html описание команды git-whatchanged.
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 27
  • +6
    для создания daemon процессов мог бы использовать вот этот пакет pypi.python.org/pypi/python-daemon
    • 0
      а есть документация по этому пакету? или какое-нибудь howto?
      • +1
        а зачем доки там всего пару строк чтоб его использовать
        import daemon from spam import do_main_program with daemon.DaemonContext(): do_main_program()
        • 0
          spam это я так понимаю типа-мой-модуль?
          • +1
            да
            • 0
              а как запустить этот скрипт?
              просто python my_script.py?
              • 0
                и еще вопросы)
                2. Как остановить?
                3. Как указать, что нужна одна копия?
                • +1
                  этот скрипт сделает fork и запустит отдельный процесс который уже будет daemon процессом!

                  чтобы остановить его нужно послать этому процессу сигнал c помощью kill

                  что касается одной копии то это уже нужно проверять самому перед запуском daemon процесса
                  • 0
                    спасибо. Попробовал, получилось.
      • 0
        Для получения изменения удобнее использовать деплой
          • 0
            gitnotify выполняет отсылку писем, а не нотификацию, как тут.

            кстати, со своей стороны, рекомендую взглянуть на bitboxer.github.com/git-commit-notifier/
            • +1
              ну вот, появился хоть один коммент, а то одни минусы :)

              Да, но мне кажется отсылка писем, в таком деле как оповещение о коммитах более удобней и функциональней.
              я как разработчик (и немного админ своих серверов) не всегда нахожусь за одним компьютером, да и вообще могу быть на телефоне, поэтому получая оповещения по почте я знаю, что не пропущу ни одно из них, на каком бы устройстве я его не просмотрел.
              Да и удобно складывать эти оповещения в папочку «на всякий пожарный»

              Я использую gitnotify вместе с etckeeper'ом на каждом сервере, и всегда знаю, что где меняется.

              За ссылку спасибо — посомотрю
              • 0
                Да, письмом надежнее. Тут скорее целью было ненавязчивое уведомление о изменениях, чтобы не затягивать время merge'a и разрешения конфликтов. В коде предусмотрел добавление новых видов уведомлений, постепенно думаю обрастет новыми экспериментами :)
          • +1
            Для реализации задачи был выбран язык Python (высокоуровневый, интерпретируемый, объектно-ориентированный и расширяемый язык программирования), так как я его не знаю.


            Что ж вы будете делать, когда языки-то кончатся…
            • +1
              Мне кажется всё правильно сделал. Я тоже стараюсь решать простые задачи на незнакомых мне языках. Во-первых, это интересно, а во-вторых, ещё и полезно для развития кругозора.
            • +2
              На заметку: чтобы не опрашивать репозитории, научите демона ловить, к примеру, широковещательные сообщения, разбрасывать которые будет post-commit хук git.
              • –2
                А если следить за десятью репозиториями и ждать коммита от неизветсного человека?
                • 0
                  Хук будет работать ровно с тех репозиториев, для которых определён. Не более.
                  А если вы действительно следите и ждёте, то libnotify не лучший вариант.

                  Я так понял, что автор упражняется, поэтому предложил ему упражнение №2 %)
              • +2
                Можно в повседневной работе использовать code review. Тогда ссылки на новые коммиты будут вам на почту падать. Будучи тимлидом, вы сможете их ревьювить. Да, кстати, и не только тимлидом.
                Цель использование данного демона?
                Все равно после коммита уже сложно что-то исправить без костылей.
                • +3
                  а мне нравится. just for fun и обучение/опыт. мог бы — плюсанул.
                  • –2
                    Доложите объём оперативной памяти, занимаемой этим демоном, и стоили ли того эти мегабайты?
                    • +5
                      def setLastCheckTime(self, time = None):
                          subprocess.check_output('touch ' + __file__, shell=True)

                      OMG… При правильной установке у файла будет овнер root:root и права rwxr--r--. Никакой touch вы ему не сделаете. Либо создайте отдельный файл и в него пишите дату последней проверки либо сохраняйте эту дату внутри объекта GitPushNotify()

                      if listGroup.has_key(i)
                      has_key уже deprecated. Пользуйтесь
                      if i in listGroup


                      В питоне по крайней мере, принято в CamelCase писать только названия классов, все остальное через_подчеркивание.

                      А в целом, если говорите что на Python раньше не писали — очень даже не плохо.
                      • 0
                        Спасибо!
                        Хранить внутри объекта не совсем верно, если демон простаивал некоторое время, то уведомления начнут поступать о коммитах потупивших только после запуска. Способ с touch был выбран, чтобы избежать дополнительных файлов, про права не подумал.
                      • 0
                        Может кому пригодится:
                        При заходе через ssh/НЕ из виртуального терминала 7 получается все так:

                        lomalkin@lomalkin-qdbook:~$ notify-send "Habr"

                        GLib-GObject-CRITICAL **: g_object_unref: assertion `G_IS_OBJECT (object)' failed
                        aborting...
                        Аварийный останов


                        Соответственно это лечится указыванием display, куда отправлять:

                        lomalkin@lomalkin-qdbook:~$ DISPLAY=:0 notify-send "Habr"
                        Теперь работает.
                        • 0
                          Добавлю, что такой же прием необходим при отправке notify-send из cron'а.
                        • 0
                          Буквально недавно написал примерно такую же штуку, только на Perl. Использует веб-хуки гитхаба (висит на порту и ждет HTTP POST от github), отсылает по XMPP заинтересованным пользователям сообщения о коммитах, тикетах, пулл-реквестах и комментариях в репозитории (на комментарий можно ответить от своего имени прямо из XMPP-клиента, если настроить OAuth-токен)

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.