Captain Hook is a tool for sending notifications based on webhooks. You connect an application with it, maps how the webhooks will produce the notifications and Captain Hook will deliver a notification when a webhook arrives.
This is a 100% Java application so you can just do the idiomatic mvn package
.
TODO
Captain Hook allows three different ways of delivering a notification:
- by taking a webhook and producing events that can be subscribed for delivering notifications
- by taking an event and deliver notifications to its subscribers
- by taking the notification as is
When a webhook is captured, it will be analyzed in order to make an event from it. Usually we can't change a webhook's payload so any query parameter sent will be converted to a label. So if the crew receives a webhook with foo=bar
in the url, the webhook will have a label foo
with a value bar
. This will help the crew to differentiate boarded webhooks to produce the events.
Events are a simple structure composed by:
name
: what differentiates this event from other eventslabels
(optional): a set of labels that can be used for filtering if you need to be more specific than just a typetitle
(optional): the title of this event, it will be used for notification purposesmessage
: the message that describes this event, used for notificationsurl
(optional): a url that contains more information about this event, used for notifications as well
A Notification is the result of attaching an Event, to an Address. It contains:
title
(optional): the title of this event, it will be used for notification purposesmessage
: the message that describes this event, used for notificationsurl
(optional): a url that contains more information about this event, used for notifications as welldestination
: contains the address that should receive the notificationpriority
: the priority of this notification (low (-1)
,normal (0)
orhigh (+1)
)labels
(optional): a set of labels that adds more context to the notification
Addresses are composed by a channel name and a target destination. On top of that, Transmitters are responsible for reaching the address to deliver the notification.
For example, the address my_chat:some-user
will be reached by a Transmitter responsible for the channel my_chat
that will pass the notification to the target some-user
. This is totally dependent on the channel type as each platform has its own way of identifying targets (user, chat room, broadcast channel, etc.).
Captain Hook expects a yaml file containing:
- transmitters
- event subscriptions
- webhook mappings
The file can be passed by either:
-Dconfig.file=/path/to/file
JVM parameterCONFIG_FILE=/path/to/file
environment variable
Every sensitive configuration, such as passwords and tokens, can be provided through four ways:
- Specifying the value in the configuration file
token: token_in_plain_text
# using a key is also a valid way
token:
value: token_in_plain_text
- Specifying an environment variable that contains the value
token:
env: ENVIRONMENT_VARIABLE
- Specifying a JVM property that contains the value
token:
property: some.property
- Specifying a file that holds the value
token:
file: /path/to/file
Why specifying a file? In a Kubernetes/OpenShift environment you might want to use a Secret in order to store sensitive values since Secrets are mounted in a RAM-backed filesystem so the contents are always volatile.
Every configuration that accepts a template can be set by either passing the template as is or passing the file that should be loaded. The relative directory is configured by the property templates.dir
which can be set by either:
-Dtemplates.dir=/path/to/the/directory
JVM parameterTEMPLATES_DIR=/path/to/the/directory
environment variable
Selectors allow you to filter webhooks and events based on a set of labels attached to them. Currently a selector can be created by supplying the labels you need to filter and the value that should be or not present:
- To specify the value, pass it as is
foo: bar
- To specify the label should hold a value, pass a
*
foo: *
- To specify the label should not hold a value, pass a
!
foo: !
- To specify multiple allowed values for a label, separate them with a
|
foo: bar|baz
Back to the common Transmitter configuration, every configuration must include the key type
so the Captain can pass the right command to the crew. You can have as much as Transmitters you need as long as they have different names.
Telegram Bots are a nice way to deliver notifications, you can create a bot and have a token in less than one minute. All you need is telegram
as type
, a sensitive input as token
and, optionally, a Freemarker template for how to structure the message.
transmitters:
my_telegram_bot:
type: telegram
token: 999999999:01234567890ABCDEFGHIJ-KLMN
template: |
${(title)!}
${message}
${(url)!}
All notification attributes can be used as variables.
Pushover is a notification service available for almost every platform. All you need is pushover
as type
and a sensitive input as token
.
transmitters:
pushover:
# remember: type is required, even if it is the same name as the transmitter
type: pushover
token:
value: lanrbhgjaiou4872yuijuqy68f7uhiadyg78u3
You can also uses a generic http call to forward the notification. Remember, Captain Hook only cares about delivering notifications, integration between systems is not the focus on this ship.
In case you wanna use the HTTP Transmitter, let Captain know, besides http
as type
:
url
: the actual url that will receive the notificationheaders
: the map containing any additional headerspayload
: the payload definition that will be sent
transmitters:
my_awesome_receiver:
type: http
url: https://my.endpoint.integration/${target}
headers:
from: captain-hook
payload:
message: ${message}
Notice that any value can be evaluated as a template, even the headers. The payload will be sent as JSON.
This transmitter is good if you need to separate the notification at the endpoint. You could easily create one for Telegram, Pushover, Slack of any of those platforms that offers a web api.
transmitters:
telegram:
type: http
url: https://api.telegram.org/botYOUR_BOT_TOKEN_HERE/sendMessage
payload:
chat_id: ${target}
text: ${message}
pushover:
type: http
url: https://api.pushover.net/1/messages.json
payload:
token: YOUR_TOKEN
user: ${target}
message: ${message}
title: ${title}
In case you just need to route a notification to an endpoint, you can use a router
Transmitter. This transmitter will use full url's as targets and the same configuration options as the HTTP transmitter for each route:
transmitters:
to:
type: router
routes:
requestbin:
url: https://YOUR_REQUEST_BIN_ID.x.pipedream.net
payload:
notification:
message: ${message}
title: ${title}
url: ${url}
telegram:
url: https://api.telegram.org/botYOUR_BOT_TOKEN/sendMessage
payload:
# sends every notification to this chat id (private or group)
chat_id: YOUR_CHAT_ID
text: ${message}
In this example, you could only refer to the address to:requestbin
and the notification will be sent to your RequestBin endpoint. Following the same logic, using the address to:telegram
will send notifications to the specified Telegram chat.
Virtual addresses can be used to shorten an address or to group multiple ones. If you often type my_chat:my-user
you can short it to something like me
and map it to that address.
The Virtual Address transmitter allows you to create virtual addresses, just use the type
as virtual
and pass the mappings as the addresses
:
transmitters:
virtual:
type: virtual
addresses:
# the key can have the same name as a transmitter
pushover: pushover:my-long-pushover-key
my_telegram: telegram:99999999
# you can have more than one address pointing to the same virtual address
test:
- virtual:pushover
- virtual:my_telegram
If you create a transmitter named default
, it will be used to handle addresses without specifying the channel. So when you subscribe the address my_team
it will be converted to default:my_team
and the default
transmitter will be used.
A good tip is to define a virtual
transmitter as the default one so you can use virtual addresses by default and keep your configuration clean.
To configure subscriptions, you need to inform the event type, the labels that should be present on the event and the destination to send the notification. Only the destination is required.
subscriptions:
# any event of name test will be sent to the virtual address test
- name: test
destination: test
# any event with the label from=nexus will be sent to the telegram:devs address
- selector:
from: nexus
destination: telegram:devs
# any event will be sent to the dump virtual address
- destination: dump
To configure the webhooks, you need to inform how the payload will be converted into an event. This is the structure of a webhook mapping:
webhooks:
- selector:
# the url should contain from=test as a query string
# or the header "from" should contain the value "test"
from: test
event:
labels:
# the /bar payload attribute will be used as the label value
foo: ${foo}
# nested attributes are also allowed, everything from Freemarker can be used here
# just try to stick with short values for labels
category: ${data.info.category}
# name and message are required attributes
name: test
message: Test event from ${person.contact.phone}
The selector
will be used to check if the webhook should be processed by this mapping. The event
is the structure of the event that will be produced. Note that if you don't define a selector, every webhook will be processed by the same mapping.
Bellow are some examples of how to use Captain Hook with known tools to leverage their notification capabilities.
webhooks:
- selector:
# GitLab sends this header so we can grab it on the selector
X-Gitlab-Event: Push Hook
event:
# use anything for labels that makes easy to make good filtering
labels:
source: gitlab
type: push
project: ${project.name}
ref: ${ref}
user: ${user_username}
name: gitlab.push
title: New push - ${project.name}
url: ${project.web_url}
message: |
${total_commits_count} commits pushed by ${user_name}
This example will raise an event every time a GitLab push hook is received. Since the labels provide information about the project, branch and user, we could have subscriptions for those specific labels as well:
- selector:
source: gitlab
# virtual address
destination: pushover_gitlab
- selector:
source: gitlab
type: push
project: captain_hook
ref: refs/heads/master
# virtual address
destination: captain_hook_team
With this example, every event from gitlab will notify the pushover_gitlab
address but, if the event is also for a push on the master
branch of the captain_hook
project, the event will also notify the captain_hook_team
virtual address.
TODO