По мере того, как вы продвигаетесь по карьерной лестнице в качестве разработчика, я думаю, что все старшие разработчики могут понять одну вещь. Чем больше опыта и выше вы поднимаетесь по служебной лестнице, тем меньше у вас времени на написание кода. Быть штатным инженером означает, что вы работаете по совершенно другому графику над совершенно разными задачами. Просто загляните на замечательный сайт staffeng.com и особенно на статью Архетипы персонала.

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

Мое самое любимое занятие во время Hackday - это Slack-боты! Они, как правило, довольно маленькие, поэтому вы можете закончить в течение 24 часов, решить конкретную проблему простым решением и отлично подходят для демонстрации в Slack. Пару дней назад я решил найти способ быстро узнать, кто дежурит в определенной команде, и узнать их номер телефона. Мы используем PagerDuty для ротации по вызову, планирования и оповещения, поэтому я приступил к созданию бота Slack с Serverless Framework, который извлекал данные из PagerDuty API и отвечал, кто дежурит по каналу Slack.

Заставляем вашего бота говорить

Прежде всего, давайте убедимся, что у нас налажено общение, заставив бота реагировать на сообщение и отвечать простым «Hej Världen» («Привет, мир» по-шведски).

Начните с создания вашего проекта и установки зависимостей

$ npm init
$ npm i -D serverless
$ npm i @slack/web-api node-pagerduty

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

Serverless Framework использует файл с именем serverless.yml для настройки облачного провайдера, всех функций Lambda и других ресурсов, которые будут созданы при развертывании. Давайте добавим один с некоторыми базовыми настройками и функцией-обработчиком, принимающей POST.

После этого мы можем создать простой файл-обработчик, который регистрирует событие и просто отвечает HTTP 200.

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

Давайте развернем эту первую начальную версию с npx и без сервера, чтобы создать необходимые ресурсы и получить URL-адрес шлюза API для нашей функции Lambda.

$ npx sls deploy

Создайте приложение Slack на странице https://api.slack.com/apps/new, перейдите в раздел Подписки на события и включите эту функцию. Вам необходимо ввести URL-адрес вашего бота, который вы можете найти в выходных данных бессерверного развертывания, и вы также должны подписаться на события бота app_mention и message.im .

После правильной настройки он будет выглядеть так

Перейдите к «OAuth & Permissions» и добавьте «chat: write» в области Bot Token Scopes, затем перейдите на «App Home» в меню слева и включите «Всегда показывать моего бота как подключенного к сети». , «Вкладка« Сообщение »» и «Разрешить пользователям отправлять команды и сообщения с косой чертой из вкладки сообщений» для поддержки прямого обмена мгновенными сообщениями с приложением, а не только в сообщениях канала. Теперь вы готовы добавить приложение в Slack. Для этого выберите «Установить приложение» и установите его на свое рабочее место. Вы получите обратно токен OAuth пользователя бота и сохраните его где-нибудь, так как он нам понадобится позже.

В настоящее время приложение мало что делает, поэтому давайте заставим его реагировать и отвечать. Одна вещь, которую вы усвоили при настройке, заключается в том, что вам нужно ответить на сообщение Slack в течение 3 секунд, иначе пользователь получит сообщение об ошибке. Учитывая холодный запуск Lambda и время для вызова API-интерфейсов PagerDuty и Slack, это сообщение об ошибке возникает довольно часто. Это легко исправить, установив «async: true» в файле serverless.yml в конфигурации вашей функции, что заставит AWS API Gateway отвечать HTTP 200 сразу после запроса. . Асинхронный вызов плохо сочетается с задачей Slack, поскольку задача всегда синхронна, что означает, что вы должны включать асинхронный вызов только после того, как задача была завершена. Еще одним следствием перехода на асинхронный вызов является то, что интеграция AWS API Gateway изменяется с LAMBDA_PROXY на LAMBDA, а Serverless Framework добавит шаблоны запросов, которые автоматически анализируют тело в JSON и изменяют структуру события. Это имеет последствия позже при проверке подписей в Slack.

После удаления парсинга JSON и добавления запроса обратно в Slack мы получаем следующее:

Одна вещь, которую вы могли заметить, заключается в том, что при записи боту в IM он заходит в бесконечный цикл, реагируя на собственное сообщение. Фрагмент содержит оператор if, позволяющий игнорировать сообщения от самого себя. Попробуйте! Это похоже на это

Обеспечение вашей интеграции

Последнее, что вам нужно, - это утечка данных или открытие для спама с помощью хорошо продуманных вредоносных запросов. Slack предоставляет два способа защиты вашей интеграции; первый - это Verification Token, который устарел, а второй - это проверка подписи с использованием секрета подписи. Давайте перейдем к поддерживаемому, а не устаревшему методу.

Так как же проверить подпись? В Slack есть отличное руководство о том, как проверить подпись, но немного о реальном коде в библиотеках. Глубоко внутри пакета @ slack / events-api есть реализация, но для ее выполнения требуется установка сервера. Мы можем просто сделать свои собственные.

Для проверки подписи вам нужны заголовки «X-Slack-Signature», «X-Slack-Request-Timestamp» и текст в виде строки. Однако из-за использования асинхронного вызова и анализа тела JSON API-шлюзом необходимо JSON.stringify () тело перед передачей его этому методу. Сначала это работает, пока кто-то ™ не решит отправить сообщение, содержащее символ Юникода. Вдруг подписи не совпадают. Единственный способ исправить это - использовать фактическое необработанное тело запроса, что означает, что вам нужно настроить шаблон запроса.

Чтобы настроить шаблоны запросов, вы можете скопировать шаблоны по умолчанию из конфигурации шлюза API и добавить следующую строку

“rawBody”: “$util.escapeJavaScript($input.body)”,

Добавьте два шаблона запроса (application / json и application / x-www-form-urlencoded) в свою базу кода и добавьте следующие строки в конфигурацию события http в вашем файл serverless.yml ниже «async: true».

integration: lambda
request:
  template:
    application/json: ${file(templates/json)}
    application/x-www-form-urlencoded: ${file(templates/form)}

В обработчике теперь доступен «event.rawBody», содержащий необработанное тело в виде строки, которая будет использоваться при проверке подписи. В приведенном выше фрагменте также явно устанавливается интеграция с лямбда-выражением, что ранее подразумевалось только с помощью директивы async.

Подключение к PagerDuty

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

Получив представление о том, как решить проблему, вы начинаете искать доступные ресурсы и API. Отличный источник - это документация по API, а если есть API-обозреватель, это даже лучше. PagerDuty имеет отличную документацию и отличный проводник по API. Просматривая документацию по API, было два метода, которые, казалось, достигли того, что я хотел: Метод по вызову и Список пользователей по вызову по расписанию. Ни один из этих методов не вернул фактическую контактную информацию пользователя, только ссылку на пользователя или ссылку на контактную информацию. Последующий вызов API был необходим для получения контактной информации, и лучше всего для этого подходил Список методов контакта пользователя. Я решил использовать Список пользователей по вызову для расписания, но в ретроспективе Метод по вызову был бы более подходящим, поскольку он принимает несколько расписаний в качестве критериев поиска, что изначально не входило в сферу охвата. Живешь, учишься.

Следующий шаг - покопаться в PagerDuty SDK и найти эквивалентные методы.

schedules.listUsersOnCall(id, qs);
users.listContactMethods(id);

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

Для поддержки вышеуказанных критериев мы можем поместить команды в файл json со следующей структурой.

[
  {
    "name": "blocket",
    "schedules": ["P9XYZ9K", "PYXYZGN", "P3XYZ6F"],
    "tags": ["blocket-all", "all-blocketeers"]
  },
  {
    "name": "aftonbladet",
    "schedules": ["P1XYZ1L"],
    "tags": ["ab"]
  }
]

Нам нужно иметь возможность получать сообщение из резервной копии, анализировать названия команд и альтернативные имена и фильтровать список команд на основе этих ключевых слов.

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

Затем мы вызываем пользователей для всех расписаний в отфильтрованных командах, получаем методы взаимодействия с пользователем, форматируем ответ и отправляем его обратно в резерв.

Задача была разделена на два метода: один для цикла команд и возможных множественных расписаний для этой команды, а другой для получения информации о дежурном пользователе и контактных данных. Вы также можете заметить, что PagerDuty SDK внедряется, а не создается. Оба внешних SDK, PagerDuty и Slack, создаются в коде обработчика, а затем вводятся в функции, чтобы иметь возможность имитировать их и модульно тестировать все части лямбда-функции.

Я фанат функционального кодирования и всегда стараюсь использовать map / filter / reduce вместо циклов. Если вы не знакомы с этими функциями, я рекомендую проверить статью Etienne Talbot Упростите свой JavaScript.

Весь код доступен по адресу https://github.com/schibsted/sls-oncall.

Давай проверим! Вот как это выглядит!

Какой у вас следующий проект Hackday? Какие API-интерфейсы, по вашему мнению, было бы круто склеить вместе или, может быть, даже сделать из него бота для Slack? Теперь у вас есть все необходимое для начала!

PS: мы набираем сотрудников и занимаем интересные должности во всех наших филиалах в Скандинавии и Польше. Ознакомьтесь с нашими открытыми вакансиями на https://schibsted.com/career/.