Recently, service for command communication
Slack has become very popular. Out of the box, it has a considerable number of integrations
with various services + a fairly convenient external API. But with all this, free accounts have a 5 integration limit. We hooked github, newrelic + a couple of boards with trello and that's it, the number of them ended. You can use the universal
Incoming WebHook , but it itself has its own format and is in no way compatible with other services. But the programmer would not be a programmer if he did not solve this problem.
The solution is simple as a hammer. We accept hooks from services ourselves, we process and throw in Slack in the form we need.
Along with the integration,
hammock was found in the list, which is written in PHP and has some
set of plug-ins , but this solution was not particularly liked. Although there are ready-made integrations, but alas, there is no one that is needed, and since I am familiar with PHP at the level of reading the code and “correcting something according to the directory,” I didn’t want to write my own.
Therefore, I made decisions to write my service. I decided to build completely on a modular basis: the core and separate modules for “input” and “output”. Ruby was used as a language, its dynamic nature was very helpful in the implementation of the plan.
So, welcome
')
Hooksler
Allows you to assemble a service with a minimum of code for receiving notifications and sending them further. To configure using your DSL:
require 'hooksler/slack' require 'hooksler/newrelic' require 'hooksler/trello' require 'dotenv' Dotenv.load Hooksler::Router.config do secret_code 'very_secret_code' host_name 'http://example.com' endpoints do input 'simple', type: :simple input 'newrelic', type: :newrelic input 'trello', type: :trello, create: false, public_key: ENV['TRELLO_KEY'], member_token: ENV['TRELLO_TOKEN'], board_id: ENV['TRELLO_ID1'] output 'black_hole', type: :dummy output 'slack_out', type: :slack, url: ENV['SLACK_WEBHOOK_URL'], channel: '#test' end route 'simple' => 'slack_out' route 'trello' => ['black_hole', 'slack_out'] route 'newrelic' => ['black_hole', 'slack_out'] end
At the beginning, I / O points are declared, each has its own name and type, and may also contain additional parameters for initialization. The following are the routes. You can specify in one form: one to one, one to many, and vice versa.
Also on each route, you can hang filters that can both modify the message and filter it. Thus, we obtain a sufficiently flexible kernel for routing messages from point A to point B.
Messages inside are transmitted in the internal representation, while it is known from which service (its type) it was received + the original message. Upon receipt, typical fields are filled in: user, text, title, link, level. In the future, they can be used to generate a notification.
Currently fully implemented, tested and covered with tests core. Several integrations have also been implemented: trello, newrelic, slack. Its integration is very easy to write.
Some practice
Receive messages
For example, let's make a module that allows placing the body of a POST request in the message field.
class DummyInput extend Hooksler::Channel::Input register :dummy def initialize(params) @params = params end def load(request) build_message({}) do |msg| msg.message = request.body.read end end end
We declare a class and expand it with the appropriate module. Then register his name. All after that we are ready to receive and process incoming data. Request processing is performed in the
load method, which takes only one parameter — an object of the class
Rack :: Request . We do not need any complex processing, so we immediately create a message and fill in the field. After that, it will go further along the routes described in the configuration. For sending, several messages can be created at once, i.e. the
load method returns an array. In the future, each object is processed separately.
Sending messages
Just as easy to make a module for sending, which will allow us to see the messages received in the console:
class DummyOutput extend Hooksler::Channel::Output register :dummy def initialize(params) @params = params end def dump(message) puts "--
Perform similar actions as for the incoming, just select the appropriate expansion module. Sending, in our case, output to the console, is performed in the
dump method. The name of the method is controversial, but send was already taken, did not want to override.
Now we will collect all this and describe the routes:
Hooksler::Router.config do secret_code 'very_secret_code' host_name 'http://example.com' endpoints do input 'in', type: :dummy output 'out', type: :dummy end route 'in' => 'out' end
Specify the code that is used to generate the paths and the host on which our service will hang. Run and ready. The final paths can be viewed by contacting
http://example.com/_endpoints_ , the response will be JSON. A more detailed example can be found in the DEMO application:
github.com/hooksler/hooksler-demoThus, without much effort, you can set up sending messages simultaneously to different points: receive changes from Trello, send them to Slack, or send especially important ones (for example, containing keywords or tags) to the phone via push. You can come up with a bunch of schemes, the benefit of the foundation is flexible.
More practical example
The other day there was a task to automate the process of inviting users to Slack. Manually adding each one is long and tedious, and you cannot make an open registration out of the box. On the Internet there is a ready-made form for nodejs. But since I already have a working hooksler decided to do on it. To begin with, you need to somehow get the correct mail, for this, I took the opportunity of Mandrill to wrap up incoming messages in Webhook (just what the doctor ordered). Next, create an inbox, configure Webhook, and write our receiver:
require 'hashie' module Hooksler module Mandrill class Input extend Hooksler::Channel::Input register :mandrill def initialize(params) @params = Hashie::Mash.new(params) end def load(request) return unless request.content_type == 'application/x-www-form-urlencoded' action, payload = request.POST.first return unless action == 'mandrill_events' payload = MultiJson.load(payload) payload.map do |event| build_message(event) do |msg| begin method_name = "for_
We accept events, we wrap up in Message and helmet on. Now we need the code that will perform inviting users:
class SlackInviteOutbound extend Hooksler::Channel::Output register :slack_invite def initialize(params) @params = params end def dump(message) return unless message.source == :mandrill email = message.raw['msg']['from_email'] url = "https://
We accept the message, check that it came from Mandrill, receive an email, request and user invited. At the same time, we are sure that the box is valid.
As a final touch, routing setup:
endpoints do input 'slack_invite', type: :mandrill output 'slack_invite', type: :slack_invite, team: 'myteam', token: 'mysupersecrettoken' end route 'slack_invite' => 'slack_invite'
Run and enjoy the process.
Finally
I have been using this solution for some time already, while there were no problems - everything runs steadily. The only thing for trello is that not all cases are processed, it’s too many different types of notifications. Also, for Slack, its own formatting modules were made, who are interested can see
an example here .
In the future, plans to expand the number of adapters for receiving and sending messages. I hope this decision will be useful to someone else.
Criticism and suggestions are welcome, error messages in the text in a personal.
Hooksler itself and adapters are available on Github:
github.com/hooksler