Выпуск 20. Октябрь 2014

Локальная установка и использование Perl-модулей | Содержание | Введение в Rose::DB::Object

Работа с API GitHub в Perl

Github один из самых популярных сервисов для создания, распространения и совместной работы с программным обеспечением. GitHub обладает превосходным API, позволяя автоматизировать задачи администрирования и тестирования, а также создавать уникальные сервисы, приносящие радость разработчикам. И, как всегда, CPAN предлагает отличный выбор библиотек для работы с GitHub API в Perl.

GitHub API

Перед обзором средств для работы с GitHub API необходимо ознакомиться с тем, что оно из себя представляет. Любое обращение к API делается с помощью http-запросов к сервису api.github.com, поэтому начать изучение можно имея под рукой curl или аналогичную утилиту. Сервис возвращает ответ в формате JSON, причём немаловажное значение имеют и http-заголовки ответа. Например, информацию о пользователе можно получить, выполнив GET-запрос по URI /users/:user:

$ curl -i https://api.github.com/users/octocat

HTTP/1.1 200 OK
Server: GitHub.com
Date: Mon, 06 Oct 2014 06:18:13 GMT
Content-Type: application/json; charset=utf-8
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1412579893
X-GitHub-Media-Type: github.v3

{
  "login": "octocat",
  "id": 583231,
  "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=2",
  "gravatar_id": "",
  "url": "https://api.github.com/users/octocat",
  ...
}

Здесь для простоты удалены некоторые заголовки и часть ответа. Как и ожидалось, тип содержимого ответа — это application/json. Заголовки, начинающиеся с X- не относятся к спецификации HTTP и в данном случае несут информацию, относящуюся к API сервиса:

  • X-GitHub-Media-Type сообщает, что используется третья версия API,

  • X-RateLimit-... информирует о том, каков ваш лимит на обращения к данному API.

Итак, как уже удалось выяснить, GitHub API предоставляет возможность выполнять некоторые запросы анонимно, но ограничивает количество таких обращений до 60 за час. Для того, чтобы увеличить данный лимит, требуется выполнять авторизацию. Для авторизованного клиента лимит увеличивается до 5000 обращений за час. На данный момент GitHub предоставляет несколько способов авторизации:

  • Basic-авторизация — каждый запрос сопровождается указанием имени зарегистрированного пользователя и пароля;

  • OAuth-авторизация — запрос сопровождается указанием ключа доступа (access token), который даёт определённый набор прав.

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

Ключи доступа

Ключи доступа позволяют авторизовать ваше приложение на выполнение ограниченного набора действий (scopes), как то чтение/запись репозиториев, создание/удаление заметок gist и т.п. Ключи доступа можно отзывать в случае компрометации. Всё это делает их довольно безопасным средством для работы с API. Существуют два подхода к созданию ключей.

Персональные ключи доступа

Это самый простой способ получения ключа доступа для вашего приложения. С помощью веб-интерфейса настроек приложений создаётся ключ доступа и указываются те права, которые получает приложение. С использованием подобных ключей вы можете решать задачи по администрированию или тестированию ваших репозиториев, получения публичной информации о других репозиториях и пользователях.

Регистрация приложения

Регистрация приложения — это способ, который позволяет получать ключи доступа в соответствии со спецификацией OAuth2. Вы регистрируете приложение на GitHub. Пользователь GitHub, который хочет воспользоваться сервисом вашего приложения, предоставляет требуемые разрешения, и GitHub генерирует соответствующий ключ доступа для вашего приложения. В основном такой подход используется для веб-приложений, в случае если требуется аутентифицировать GitHub-пользователя или приложение должно действовать от имени пользователя (как например, Travis-CI).

Модули GitHub API на CPAN

На данный момент наиболее популярны два модуля для работы с GitHub API: Pithub и Net::GitHub. Оба модуля обладают практически полным набором функций и похожи как братья близнецы, можно отметить лишь два заметных отличия:

  • Pithub больше ориентирован на ООП-подход в программировании. Например, результат обращения к API — это объект класса Pithub::Result, который, в частности, поддерживает метод count, чтобы получить число элементов, и метод next, чтобы получить следующий элемент. В то время как Net::GitHub возвращает голый результат, например, в виде хеша.

  • Net::GitHub, наряду с ключами доступа, поддерживает и basic-авторизацию, что может быть полезно, чтобы сгенерировать новый ключ доступа. Pithub работает только с ключами доступа.

Возможно, это дело вкуса, но Pithub показался мне более удобным и приятным в использовании, поэтому остановимся подробно именно на нём.

Структура модулей Pithub

API GitHub можно разделить на несколько основных разделов, каждый из которых представляется соответствующим модулем:

  • Pithub::Events — API событий, позволяющая отслеживать ту или иную активность на сайте GitHub;

  • Pithub::Gists — API выжимок (gist) для получения и манипуляций с выжимками;

  • Pithub::GitData — API данных для работы с сущностями git-репозиториев: блобы, деревья, коммиты, теги и ссылки;

  • Pithub::Issues — API баг-трекера (ошибки, вопросы и проблемы);

  • Pithub::Orgs — API организаций для получения и изменения информации организаций, а также управления командами и участниками;

  • Pithub::PullRequests — API запросов на слияние, включая и работу с комментариями к запросам;

  • Pithub::Repos — API репозиториев, содержащее обширное число операций по работе с репозиториями;

  • Pithub::Users — API пользователей для получения и изменения информации о пользователях GitHub.

  • Pithub::Search — API поиска по GitHub.

Как видно из списка, в Pithub пока отсутствуют некоторые новые разделы GitHub API, такие как Активность, включающая несколько подразделов: уведомлений, ATOM-фидов и других. Также отсутствуют Markdown API, Meta API, RateLimit API и некоторые другие. Кроме того Pithub::Search использует устаревший legacy-интерфейс поиска. Тем не менее, как будет показано позже, с помощью модуля Pithub::Base вы можете компенсировать отсутствие готового нужного раздела API и напрямую делать вызовы GitHub API.

“Hello, GitHub!”

Итак, вы успешно зарегистрировали нового пользователя на GitHub (увы, некоторые просчёты могут привести к блокировке учётной записи, так что лучше перестраховаться) и создали первый персональный ключ доступа. Можно приступать к созданию своего первого приложения.

use DDP;
use Pithub;

# Ключ доступа берем из переменной окружения
my $p = Pithub->new(
    token => $ENV{GITHUB_TOKEN}
);

# Исчерпывающая информация о пользователе octocat
my $result = $p->users->get( user => 'octocat' );

# Вывод декодированной структуры ответа
p $result->content;

# Вывод количества оставшихся запросов к API
p $result->ratelimit_remaining;

Перед запуском скрипта необходимо экспортировать переменную окружения GITHUB_TOKEN, в которую нужно поместить значение ключа доступа:

$ export GITHUB_TOKEN=deadbeeffacecafe
$ perl hello_github.pl

\ {
    avatar_url  "https://avatars.githubusercontent.com/u/583231?v=2",
    bio         undef,
    blog        "http://www.github.com/blog",
    company     "GitHub",
    created_at  "2011-01-25T18:44:36Z",
    email       "octocat@github.com",
    ...
}
4999

В данном примере мы создавали базовый объект Pithub $p, а затем получали доступ к методам объекта Pithub::Users с помощью метода users. Точно такой же результат можно было получить, создав Pithub::Users напрямую:

my $u = Pithub::Users->new(
    token => $ENV{GITHUB_TOKEN}
);

my $result = $u->get( user => 'octocat' );

Такой же подход можно использовать и для всех других субмодулей Pithub.

Работа со списками

Некоторые вызовы API возвращают не один объект, а список JSON-объектов. Как, например, запрос списка репозиториев пользователя или организации:

use DDP;
use Pithub;

my $r = Pithub::Repos->new( token => $ENV{GITHUB_TOKEN} );

my $result = $r->list( org => 'PadreIDE' );

p $result->count;

while ( my $row = $result->next ) {
    p $row->{name};
}

В данном примере запрашивается список репозиториев организации PadreIDE. Запрос возвращает список из 30 репозиториев, но самих репозиториев на самом деле 100. Связано это с тем, что GitHub по умолчанию устанавливает лимит на 30 объектов в рамках одного запроса, и для того, чтобы получить весь список объектов, можно запрашивать их постранично:

my $result = $r->list( org => 'PadreIDE' );
while ( $result && $result->success ) {
    while ( my $row = $result->next ) {
        p $row->{name};
    }
    $result = $result->next_page;
}

В данном случае метод next_page делает новый запрос к API и возвращает новую порцию данных (или undef, если данных больше нет).

Тоже самое можно получить, установив флаг auto_pagination в истинное значение, тогда метод next будет автоматически вызывать next_page при завершении списка. Кроме того, можно регулировать и количество объектов в одном ответе с помощью свойства per_page:

# включить автоматический запрос последующих страниц
$r->auto_pagination(1);

# 50 объектов на странице
$r->per_page(50);

my $result = $r->list( org => 'PadreIDE' );
while ( my $row = $result->next ) {
    p $row->{name};
}

Запросы к API, которые не реализованы в Pithub

GitHub API активно развивается, добавляются новые разделы, которые не были реализованы в Pithub. Поскольку все обращения к API выполняются через http-запросы, в Pithub существует возможность указывать нужный метод и URI запроса с помощью метода request. Рассмотрим на примере Ratelimit API, которое позволяет GET-запросом по URI /rate_limit получить информацию об актуальных значениях лимитов на API-запросы для вашего приложения.

use DDP;
use Pithub;

my $p = Pithub->new(
    token => $ENV{GITHUB_TOKEN}
);

my $result = $p->request(
    method => 'GET',
    path   => '/rate_limit',
);
p $result->content;

В данном примере мы указываем метод и путь запроса к API, и в результате получаем объект класса Pithub::Result с информацией.

\ {
    rate        {
        limit       5000,
        remaining   5000,
        reset       1412552655
    },
    resources   {
        core     {
            limit       5000,
            remaining   5000,
            reset       1412552655
        },
        search   {
            limit       30,
            remaining   30,
            reset       1412549115
        }
    }
}

Другой пример это Markdown API. GitHub позволяет преобразовывать данные, отправленные в формате Markdown, в html-формат.

use DDP;
use Pithub;

my $p = Pithub->new(
    token => $ENV{GITHUB_TOKEN}
);

my $result = $p->request(
    method => 'POST',
    path   => '/markdown',
    data => {
        text => '**Hello, World!**',
        mode => 'markdown',
    }
);

p $result->raw_content;

В данном примере выполняется POST-запрос, тело запроса в формате JSON формируется из структуры data. Обратите внимание, что для извлечения результата используется метод raw_content, поскольку получаемые данные приходят в формате text/html, а не application/json и не требуют декодирования.

<p><strong>Hello, World!</strong></p>

Практический пример: строим свой CI-сервер

Одно из интересных применений GitHub API — это возможность создания своих собственных сервисов и в частности сервера непрерывной интеграции.

В GitHub существует возможность уведомлять внешние сервисы о тех или иных событиях, происходящих в ваших репозиториях. Например, отправка коммитов в репозиторий (push). В настройках каждого репозитория в разделе WebHooks можно указать URL сервиса, который будет принимать POST-запросы с информацией о произошедшем событии.

API GitHub поддерживает установку статусов для каждого коммита. Статус — это некоторая метка, информация о состоянии репозитория и может использоваться CI-системами для обозначения того, успешно ли собирается проект или того, что данный коммит приводит к ошибкам.

Рассмотрим простое веб-приложение на веб-фреймворке Dancer, которое может выступить в роли CI-сервера:

use Dancer;
use Pithub;
use JSON ();

post '/event_handler' => sub {
    my $payload = JSON::decode_json(request->body);

    my $user = $payload->{repository}->{owner}->{name};
    my $repo = $payload->{repository}->{name};

    # Статус сборки
    my $s = Pithub::Repos::Statuses->new(
        token => $ENV{GITHUB_TOKEN},
        user  => $user,
        repo  => $repo,
    );

    # Статус сборки - в обработке (pending)
    $s->create(
        sha => $payload->{after},
        data => {
            state => 'pending',
            description => 'starting build',
            context => 'ci/perl',
        }
    );

    # Запустить сборку
    run_ci(
        commit => $payload->{after},
        url    => $payload->{repository}->{clone_url},
        name   => $payload->{repository}->{name},
        user   => $payload->{repository}->{owner}->{name},
        cb     => sub {
            my ( $state, $url ) = @_;
 
            # Обновляем статус (failure или success)
            $s->create(
                sha => $payload->{after},
                data => {
                    state => $state,
                    description => "build $state!",
                    context => 'ci/perl',
                    target_url => $url
                }
            );
        },
    );

    "ok";
};

dance;

Итак, запускается веб-сервер, который ожидает POST-запрос по URI /event_handler, которое было указано в WebHooks репозитория. GitHub на каждый push в репозиторий будет отправлять данные на наше веб-приложение. Данные приходят в формате JSON, которые декодируются в строке 6. Из полученной структуры извлекается информация о коммите, пользователе и названии репозитория.

В строке 19 мы используем API статусов и устанавливаем для полученного коммита статус «pending», т.е. коммит находится в процессе проверки. Далее следует некая асинхронная процедура run_ci, которая клонирует репозиторий, выполняет проверку, например, сборку. И по результатам выполняет функцию обратного вызова с результатами проверки: это может быть «failure»/«error» или «success». Этот результат мы используем для обновления статуса коммита. В данном примере мы также устанавливаем target_url — это url по которому можно получить, например, логи сборки репозитория.

В веб-интерфейсе GitHub информацию о статусе можно увидеть в списке веток репозитория, где показывается последний статус для каждой ветки в виде ссылки, которую вы указали в target_url.

Таким же образом можно проверять сборку репозитория при получении запросов на слияние (pull request). В этом случае надо проверять получаемый заголовок X-Github-Event, который будет иметь значение pull_request.

Заключение

GitHub API — это уникальный сервис, открывающий поистине огромные возможности для создания полезных и практичных приложений. В статье было рассмотрено лишь несколько примеров по работе с API с помощью модуля Pithub, которые дают представление о способе его работы и интерфейсе. Более подробная информация содержится в POD-документации модуля, а информация об API GitHub — на сайте для разработчиков. Смотрите и изучайте, надеюсь, его больше никогда не заблокируют.

Владимир Леттиев


Локальная установка и использование Perl-модулей | Содержание | Введение в Rose::DB::Object
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье