hookah

package module
v3.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2023 License: MIT Imports: 16 Imported by: 0

README

Hookah

Go Report Card GoDoc CI

Hookah is a simple server for GitHub Webhooks that forwards the hooks message to any series of scripts, be they PHP, Ruby, Python or even straight up shell.

It simply passes the message on to the STDIN of any script.

Installation

From Source

Building v3 requires Go 1.20+

go install github.com/donatj/hookah/v3/cmd/hookah@latest
From Binary

see: Releases.

Basic Usage

When receiving a Webhook request from GitHub, Hookah checks {server-root}/{vendor}/{repo}/{X-GitHub-Event}/* for any executable scripts, and executes them sequentially passing the JSON payload to it's standard in.

This allows actual hook scripts to be written in any language you prefer.

For example, a script server/donatj/hookah/push/log.rb would be executed every time a "push" event Webhook was received from GitHub on the donatj/hookah repo.

Example Hook Scripts

bash + jq
#!/bin/bash

set -e

json=`cat`
ref=$(<<< "$json" jq -r .ref)

echo "$ref"
if [ "$ref" == "refs/heads/master" ]
then
        echo "Ref was Master"
else
        echo "Ref was not Master"
fi

PHP
#!/usr/bin/php
<?php

$input = file_get_contents("php://stdin");
$data  = json_decode($input, true);

print_r($data);

Note

Don't forget your scripts need to be executable. This means having the executable bit set ala chmod +x <script filename>, and having a shebang pointing to your desired interpreter, i.e. #!/bin/bash

Documentation

Standard input (stdin) contains the unparsed JSON body of the request.

Execution

The server root layout looks like {server-root}/{vendor}/{repo}/{X-GitHub-Event}/{script-name}

Scripts are executed at each level, in order of least specific to most specific. At an individual level, the execution order is file system specific and must not be depended upon.

A directory at the vendor or repo level named @@ will behave as a wildcard. As such a file named server-root/donatj/@@/pull_request_review_comment/script.sh would execute for all of @donatj's pull_request_review_comment events regardless of repo.

Error Handling

Error handlers are scripts prefixed with @@error. and function similarly to standard scripts. Error handlers however are only triggered when the executiono of a normal script returns a non-zero exit code.

Error handlers like normal scripts trigger in order up from the root to the specificity level of the script.

Example

Consider the following server file system.

├── @@error.rootlevel.sh
├── run-for-everything.sh
└── donatj
    ├── @@error.userlevel.sh
    ├── run-for-donatj-repos.sh
    ├── @@
    │   └── pull_request_review_comment
    │       └── all-of-donatjs-pr-comments.sh
    └── hookah
        └── pull_request_review_comment
            ├── @@error.event-level.sh
            ├── likes-to-fail.sh
            └── handle-review.php

The execution order of a pull_request_review_comment event is as follows:

run-for-everything.sh
donatj/run-for-donatj-repos.sh
donatj/hookah/pull_request_review_comment/likes-to-fail.sh
donatj/hookah/pull_request_review_comment/handle-review.php
donatj/@@/pull_request_review_comment/all-of-donatjs-pr-comments.sh

Now let's consider if likes-to-fail.sh lives up to it's namesake and returns a non-zero exit code. The execution order then becomes:

run-for-everything.sh
donatj/run-for-donatj-repos.sh
donatj/hookah/pull_request_review_comment/likes-to-fail.sh
@@error.rootlevel.sh
@@error.userlevel.sh
@@error.event-level.sh
donatj/hookah/pull_request_review_comment/handle-review.php
donatj/@@/pull_request_review_comment/all-of-donatjs-pr-comments.sh

In contrast, imagining donatj/run-for-donatj-repos.sh returned a non-zero status, the execution would look as follows:

run-for-everything.sh
donatj/run-for-donatj-repos.sh
@@error.rootlevel.sh
@@error.userlevel.sh
donatj/hookah/pull_request_review_comment/likes-to-fail.sh
donatj/hookah/pull_request_review_comment/handle-review.php
donatj/@@/pull_request_review_comment/all-of-donatjs-pr-comments.sh
Environment Reference
All Executions

GITHUB_EVENT : The contents of the X-Github-Event header.

GITHUB_DELIVERY : The contents of the X-GitHub-Delivery header. A Unique ID for the Given Request

GITHUB_LOGIN : The GitHub login of the owner of the repository.

GITHUB_REPO : The name portion of the repository, e.g. hookah.

GITHUB_ACTION : The action of the event, e.g. opened.

HOOKAH_SERVER_ROOT : The absolute path of the root directory of the hookah server.

Error Handler Executions

HOOKAH_EXEC_ERROR_FILE : The path to the executable that failed to execute.

HOOKAH_EXEC_ERROR : The error message received while trying to execute the script.

HOOKAH_EXEC_EXIT_STATUS : The exit code of the script. This may not be defined in certain cases where execution failed entirely.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrPathIsNotDir = errors.New("path is not a dir")

Functions

This section is empty.

Types

type HookExec

type HookExec struct {
	RootDir string
	Data    io.ReadSeeker
	InfoLog Logger

	Stdout io.Writer
	Stderr io.Writer
}

HookExec represents a call to a hook

func (*HookExec) Exec

func (h *HookExec) Exec(owner, repo, event, action string, timeout time.Duration, env ...string) error

Exec triggers the execution of all scripts associated with the given Hook

func (*HookExec) GetPathExecs

func (h *HookExec) GetPathExecs(owner, repo, event, action string) ([]string, []string, error)

GetPathExecs fetches the executable filenames for the given path

func (*HookExec) InfoLogf

func (h *HookExec) InfoLogf(format string, v ...any)

InfoLogf logs to the info logger if not nil

func (*HookExec) InfoLogln

func (h *HookExec) InfoLogln(msg string)

type HookJSON

type HookJSON struct {
	Action     string `json:"action,omitempty"`
	Repository struct {
		Name  string       `json:"name"`
		Owner HookUserJSON `json:"owner"`
	} `json:"repository"`
	Sender HookUserJSON `json:"sender"`
}

HookJSON represents the minimum body we need to parse

type HookServer

type HookServer struct {
	RootDir string

	Timeout  time.Duration
	ErrorLog Logger
	InfoLog  Logger

	sync.Mutex
}

HookServer implements net/http.Handler

func NewHookServer

func NewHookServer(rootDir string, options ...ServerOption) (*HookServer, error)

NewHookServer instantiates a new HookServer with some basic validation on the root directory

func (*HookServer) ServeHTTP

func (h *HookServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

type HookUserJSON

type HookUserJSON struct {
	Login string `json:"login"`
	Name  string `json:"name"`
}

HookUserJSON exists because some hooks use Login, some use Name - it's horribly inconsistent and a bad flaw on GitHubs part

func (*HookUserJSON) GetLogin

func (h *HookUserJSON) GetLogin() string

GetLogin is used to get the login from the data github decided to pass today

type Logger

type Logger interface {
	Printf(format string, v ...any)
	Println(v ...any)
}

Logger handles Printf

type ServerOption

type ServerOption func(*HookServer) error

ServerOption sets an option of the HookServer

func ServerErrorLog

func ServerErrorLog(log Logger) ServerOption

ServerErrorLog configures the HookServer error logger

func ServerExecTimeout

func ServerExecTimeout(timeout time.Duration) ServerOption

ServerExecTimeout configures the HookServer per-script execution timeout

func ServerInfoLog

func ServerInfoLog(log Logger) ServerOption

ServerInfoLog configures the HookServer info logger

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL