Skip to main content

Trigger webhooks with events.

Project description

Events

eventhooks triggers webhooks for web services:

  • POST web hook
  • Mattermost
  • Dockerhub

eventhooks sends emails:

  • Simple emails (requires host, port, user and password)
    • With or without TLS.
  • AWS SES emails (using boto3, requires AWS credentials)
    • Needs to be installed with aws extra: pip install eventhooks[aws]

eventhooks publishes to AMQP exchanges:

  • AMQP (using pika).
    • Needs to be installed with pika extra: pip install eventhooks[pika]
  • Install all dependencies:
    • pip install eventhooks[pika,aws]

Note:

Of course, events could do a lot more. If you have an idea, please just create an issue and describe it.

Additionally, events can be configured with relams. Realms provide a context to an event and restrict the event action by caller origin. Have a look at Understanding realms.

Idea

The idea of eventhooks is to have a single library that helps accomplishing certain tasks in case of certain events. The triggers can be anything, in fact - They entirely depend on you.

Examples:

  • Send an email in case of a failed servcie on your server.
  • Additinoally, push the log of the failed service onto an AMQP exchange.
  • Trigger a Dockerhub build of one of your docker images in case a new push to a dependent project happened.
  • Have a Mattermost bot send the notification about a finished job to your team members.
  • Add the finished job onto a specific statistics queue on RabbtMQ.

Configuration

For a more detailed configuration see further below.

There is a simple web hook:

  • WeHook:
    • Uses requests.
    • Does a POST request to a given URL.
    • Sends json data.

There are two,more specific web hooks that require more detailed configuration like setting tokens in the URL:

  • MattermostWebHook
    • Builds on top of WebHook, requires a mattermost <token>.
    • Uses URL format: <host>/hooks/<token>
  • DockerCloudWebHook
    • Builds on top of WebHook, requires a docker hub <source> and <trigger>.
    • Uses URL format: https://hub.docker.com/api/build/v1/source/<source>/trigger/<trigger>/call/

There are two email hooks:

  • SimpleEmailHook
    • Uses smtplib and requires host, port, user and password.
    • User and password are provided in the following format: user:password (see example below).
    • With port 25, set tls=False to skip authentication.
  • AwsSesEmailHook
    • Needs to be installed with aws extra: pip install eventhooks[aws]
    • Uses boto3 and requires AWS credentials (AWS access key ID and AWS secret access key).
    • If not configured otherwise, please set the required AWS region using the environment variable AWS_DEFAULT_REGION="eu-east-1".

There is a AMQP (e.g. RabbitMQ) hook:

  • RabbitMqHook
    • Uses pika and requires host, user and password.
    • The defaultvhost is /.
    • The default configuration sends messages directly to queue example_queue.

Note:

  • There is a eventhook factory, that makes a more dynamic configuration possible. For more details, see Using the eventhook factory.

Web hooks

WebHook configuration

A WebHook can be configured like this:

    from eventhooks.eventhooks import WebHook
    hit_alarm_endpoint = WebHook(
        name="",
        url="some.server.com",
    )
    # In case of some event:
    threshold = 80
    hit_alarm_endpoint.trigger(data={"event": hit_alarm_endpoint.name, "message": f"Reached '{80}'.", "area": "outside"})

MattermosWebtHook configuration

A MattermostWebHook can be configured like this:

    from eventhooks.eventhooks import MattermostWebHook
    inform_about_job_status = MattermostWebHook(
        name="job_A_last_step",
        host="mattermost.server.com",
        token="<token>",
    )
    # In case of some event:
    job_finished = True
    inform_about_job_status.trigger(data={"event": inform_about_job_status.name, "message": f"Job completed: '{job_finished}'."})

DockerCloudWebHook configuration

A DockerCloudWebHook can be configured like this:

    from eventhooks.eventhooks import DockerCloudWebHook
    dockercloud_trigger = DockerCloudWebHook(
        name="dockercloud_event",
        source_branch="master",
        source_type="Branch",
        source="<source>",
        trigger="<trigger>",
    )
    # In case of some event:
    found_new_tag = True
    if found_new_tag:
        dockercloud_trigger.trigger()

Email hooks

SimpleEmailHook configuration

A SimpleEmailHook can be configured without TLS:

    from eventhooks.eventhooks import SimpleEmailHook
    failed_service_mail = SimpleEmailHook(
        name="failed_service_checker",
        host="localhost",
        port=25,
        sender="someone@somwhere.com",
        sender_name="someone",
        recipients="mew@xyz.com, you@xyz.com",
        tls=False,
    )
    # In case of some event:
    # The event name ('failed_service_mail.name') will be used as email subject.
    failed_services = ["mongodb.service", "nginx.service", "cron.service"]
    email_body = {
        "name": failed_service_mail.name,
        "failed_services": failed_services,
    }

    failed_service_mail.trigger(data=email_body)
    # or simply
    failed_service_mail.trigger(data=",".join(failed_services))

A SimpleEmailHook can be configured with TLS:

    from eventhooks.eventhooks import SimpleEmailHook
    simple_email_trigger = SimpleEmailHook(
        name="aws_simple_email_event",
        host="email-smtp.eu-west-1.amazonaws.com",
        port=587,
        credentials="user:password",
        sender="someone@somwhere.com",
        sender_name="someone",
        recipients=["mew@xyz.com", "you@xyz.com"],
    )

Some general email connection settings can be configured using environment variables:

environment variable description default value
EVENT_MAIL_HOST Email server host. "email-smtp.us-west-2.amazonaws.com"
EVENT_MAIL_PORT Email server port. 587

Note:

  • So far emails are sent in plain text only, no option for HTML.
  • TLS is used by default and can be disabled using tls=False when initialising the SimpleEmailHook.
  • If no email subject is configured using the environment variable SUBJECT, the name of the SimpleEmailHook will be used as the email's subject by default. Of course this can be changed later on:
    # Set the email's subject.
    simple_email_trigger.email.subject = "Something else."

AwsSesEmailHook configuration

Note:

  • For this to work properly set the environemnt variable AWS_DEFAULT_REGION=us-east-1. Replace the AWS region to use by the one appropriate to your use case.

The AwsSesEmailHook can be configured like this:

    from eventhooks.eventhooks import AwsSesEmailHook
    aws_ses_email_trigger = AwsSesEmailHook(
        name="aws_ses_email_event",
        sender="someone@somwhere.com",
        sender_name="someone",
        recipients=["me@peer.xyz"],
    )

Note:

  • So far emails are sent in plain text only, no option for HTML.
  • If no email subject is configured using the environment variable SUBJECT, the name of the AwsSesEmailHook will be used as the email's subject by default. Of course this can be changed later on:
    # Set the email's subject.
    aws_ses_email_trigger.email.subject = "Something else."

Email body

Like mentioned earlier (See example configurations above), every event is essentially triggered like this:

    event.trigger(data="Found new tag for repo <some_github_repo>.")

This is also true for the SimpleEmailHook as well as AwsSesEmailHook.

Note:

  • The data argument is used as the email's body text.

Note - The hook accepts str and dict as body text:

  • event.trigger(data="Some string") (str)
  • event.trigger(data={"error": "Weird error.", "cause": "Human factor."}) (dict)
    • In this case, the JSON is indented.

Internally it works like this (simplified):

    from typing import Union
    def trigger(data: Union[dict,str]):
        ...
        # Set the email body with the 'data' argument.
        email.body_text = data
        email.send_mail()
        ...

RabbitMqHook configuration

The RabbitMqHook can be configured like this:

    from eventhooks.eventhooks import RabbitMqHook
    rabbitmq_trigger = RabbitMqHook(
        name="failed_services_event",
        host="rabbitmq.company.com",
        user="username",
        password="secur3_!Password!",
        exchange="amqp.topic",
        routing_key='company.dep2.failed_services.serverA',
    )

Understanding realms

Realms provide a context to an event and restrict the event action by caller origin.

  • A realm can be a simple string, which is set on initialization of an event.
  • Specifying a realm with an event, requires the realm to be passed with the trigger() to actually trigger the event.
  • Not defining any realms will ignore the feature entirely.

Example: All the examples given above have been defined without using realms. Now, let's imagine, you have built a project that watches a github repository:

  • In case of new pushes to master:
    • Trigger a dockerhub build.
  • In case of a new tag:
    • Trigger a dockerhub build.
    • Notify team members by mail.

You can now define 3 events:

  • DockerCloudHook (as can be seen above) to trigger the build of the latest docker image based on master of the github repository you watch.
  • DockerCloudHook (as can be seen above) to trigger the build of a tagged docker image based on new tags of the github repository you watch.
  • SimpleEmailHook (as can be seen above) in case of new tags in the github repository.
    from eventhooks.eventhooks import DockerCloudWebHook
    from eventhooks.eventhooks import SimpleEmailHook

    latest_build= DockerCloudWebHook(
        ...,
    )
    tag_build= DockerCloudWebHook(
        ...,
    )
    email_team = SimpleEmailHook(
        ...,
        recipients=["developers@company.com", "head_of_devs@company.com"],
    )
    events = [latest_build, tag_build, email_team]

If you now just looped over the list of events in case of a new push to master, you would end up triggering the events defined for new tags as well.

    # Loop over the list of events.
    def trigger_events(data: str=""):
        for event in events:
            event.trigger(data=data)

    trigger_events(data={"msg": "Push to master found."})

This is not what we want,

When using realms, the realm passed to an event's trigger() method is compared against the realms given on initialization. Only if the given realm is found in the list of realms the event is actually triggered.

    from eventhooks.eventhooks import DockerCloudWebHook
    from eventhooks.eventhooks import SimpleEmailHook

    latest_build= DockerCloudWebHook(
        ...,
        realms = ["GITHUB_MASTER"],
    )
    tag_build= DockerCloudWebHook(
        ...,
        realms = ["GITHUB_TAG"],
    )
    email_team = SimpleEmailHook(
        ...,
        recipients=["developers@company.com", "head_of_devs@company.com"],
        realms = ["GITHUB_TAG"],
    )
    events = [latest_build, tag_build, email_team]

Now, when a new push to master is found, you would pass the configured realm GITHUB_MASTER as well. The 2 events preserved for new github repository tags are ignored - They do not support the given realm GITHUB_MASTER, only GITHUB_TAG.

    # Loop over the list of events and pass realms.
    def trigger_events(data: str="", realm=None):
        for event in events:
            event.trigger(data=data, realm=realm)


    trigger_events(data={"msg": "Push to master found."}, realm="GITHUB_MASTER")

Using the eventhook factory

Instead of configuring events as show above, the event_heper can be used to return an already configured event.

The type in the event's configuration needs to be one of:

  • WebHook
  • DockerCloudWebHook
  • MattermostWebHook
  • SimpleEmailHook
  • AwsSesEmailHook
  • RabbitMqHook
from eventhooks import event_helper

event_config = {
    "type": "SimpleEmailHook",
    "sender" mail@company.com,
    "sender_name": "maintenance",
    "recipients": ["devs@company.com"],
    "realms": ["log_contains_key"],
}

event = event_helper.eventhook_factory("event_name", event_config)

Logging

Below is a small example of how to set a common log format when using the eventhooks module. The idea is to have a common log format throughout your entire application.

import logging
import sys

import eventhooks.eventhooks
from eventhooks.mail import aws_ses
from eventhooks.mail import simple


# Your app logger.
logger = logging.getLogger("TestApp")
logger.setLevel(logging.DEBUG)
# A 'StreamHandler' printing to the terminal.
ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(logging.Formatter("%(asctime)a - %(name)s [%(levelname)s] [%(lineno)s]: %(message)s"))
ch.setLevel(logging.DEBUG)
logger.addHandler(ch)
logger.propagate = False
# The 'eventhooks' logger.
logger_ = logging.getLogger("EventHooks")
logger_.addHandler(ch)
logger_.propagate = False

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for eventhooks, version 0.2.5
Filename, size File type Python version Upload date Hashes
Filename, size eventhooks-0.2.5-py3-none-any.whl (14.2 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size eventhooks-0.2.5.tar.gz (16.9 kB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page