Compare commits

..

9 Commits

Author SHA1 Message Date
DIGVIJAY 857e708f87 Docs: HTTP request parameters documented! (#740)
* docs: HTTP request parameters
2026-02-12 23:59:28 +01:00
Adnan Hajdarevic f78eeeca7d Bump version to 2.8.3 2026-02-12 23:36:16 +01:00
Julio 8a2d3f85cd Added Printing the Raw Webhook Payload to Standard Output example (#746) 2026-01-09 16:08:29 +01:00
DIGVIJAY 34d4418840 docs: Documented cat and credential template functions (#739) 2025-11-27 22:09:50 +01:00
Jonathan Leroy 7ea2a53bbb Fix Gitea incoming webhook example and add a YAML version (#737)
* Fix Gitea incoming webhook example and add a YAML version

* Fix payload-hmac-sha256 secret attribute name
2025-08-28 23:17:53 +02:00
Tom 2da9957e86 docs: add ACR webhook to Hook-Examples.md (#731)
* add ACR webhook to Hook-Examples.md

Incoming Azure Container Registry (ACR) webhook syntax and format with link to a working docker container used to handle webhook and feed a local registry cache

* Update Hook-Examples.md

changed text according to suggestions of  https://github.com/moorereason (see PR)
2025-08-11 23:01:42 +02:00
Uğur Erdem Seyfi 5981da2717 docs: update README.md (#733) 2025-08-11 23:00:21 +02:00
Tom Hubrecht 1b13355196 fix: Trim the cat output (#720)
`os.ReadFile` includes a trailing EOL, so we have to remove it to get
the correct value
2025-01-12 12:27:16 +01:00
Tom Hubrecht eb7e8f5ba8 feat: Add two template functions (#712)
* chore: replace ioutil.ReadFile by os.ReadFile

* feat: Add two template functions

- cat:        Allows reading a value from a file
- credential: Allows reading a credential passed by systemd
2025-01-12 00:42:22 +01:00
7 changed files with 218 additions and 32 deletions
+1 -1
View File
@@ -141,7 +141,7 @@ Check out [Hook examples page](docs/Hook-Examples.md) for more complex examples
- [Using Prometheus to Automatically Scale WebLogic Clusters on Kubernetes](https://blogs.oracle.com/weblogicserver/using-prometheus-to-automatically-scale-weblogic-clusters-on-kubernetes-v5) by [Marina Kogan](https://blogs.oracle.com/author/9a4fe754-1cc2-4c64-95fc-360642b62927)
- [Github Pages and Jekyll - A New Platform for LACNIC Labs](https://labs.lacnic.net/a-new-platform-for-lacniclabs/) by [Carlos Martínez Cagnazzo](https://twitter.com/carlosm3011)
- [How to Deploy React Apps Using Webhooks and Integrating Slack on Ubuntu](https://www.alibabacloud.com/blog/how-to-deploy-react-apps-using-webhooks-and-integrating-slack-on-ubuntu_594116) by Arslan Ud Din Shafiq
- [Private webhooks](https://ihateithe.re/2018/01/private-webhooks/) by [Thomas](https://ihateithe.re/colophon/)
- [Private webhooks](https://tmertz.com/2018/01/private-webhooks/) by [Thomas](https://tmertz.com)
- [Adventures in webhooks](https://medium.com/@draketech/adventures-in-webhooks-2d6584501c62) by [Drake](https://medium.com/@draketech)
- [GitHub pro tips](http://notes.spencerlyon.com/2016/01/04/github-pro-tips/) by [Spencer Lyon](http://notes.spencerlyon.com/)
- [XiaoMi Vacuum + Amazon Button = Dash Cleaning](https://www.instructables.com/id/XiaoMi-Vacuum-Amazon-Button-Dash-Cleaning/) by [c0mmensal](https://www.instructables.com/member/c0mmensal/)
+127 -6
View File
@@ -22,6 +22,25 @@ although the examples on this page all use the JSON format.
* [Multipart Form Data](#multipart-form-data)
* [Pass string arguments to command](#pass-string-arguments-to-command)
* [Receive Synology DSM notifications](#receive-synology-notifications)
* [Incoming Azure Container Registry (ACR) webhook](#incoming-acr-webhook)
## Printing the Raw Webhook Payload to Standard Output
This hook configuration receives incoming webhook requests and prints the raw request body (payload) directly to the server's standard output (visible in the webhook process logs when running with -verbose). It is particularly useful for debugging and verifying webhook deliveries from external services.
```json
[
{
"id": "print-payload",
"execute-command": "/bin/echo",
"pass-arguments-to-command": [
{
"source": "entire-payload",
}
]
}
]
```
## Incoming Github webhook
@@ -213,7 +232,11 @@ Values in the request body can be accessed in the command or to the match rule b
}
]
```
## Incoming Gitea webhook
JSON version:
```json
[
{
@@ -228,7 +251,7 @@ Values in the request body can be accessed in the command or to the match rule b
},
{
"source": "payload",
"name": "pusher.name"
"name": "pusher.full_name"
},
{
"source": "payload",
@@ -242,12 +265,12 @@ Values in the request body can be accessed in the command or to the match rule b
{
"match":
{
"type": "value",
"value": "mysecret",
"type": "payload-hmac-sha256",
"secret": "mysecret",
"parameter":
{
"source": "payload",
"name": "secret"
"source": "header",
"name": "X-Gitea-Signature"
}
}
},
@@ -255,7 +278,7 @@ Values in the request body can be accessed in the command or to the match rule b
"match":
{
"type": "value",
"value": "refs/heads/master",
"value": "refs/heads/main",
"parameter":
{
"source": "payload",
@@ -269,6 +292,35 @@ Values in the request body can be accessed in the command or to the match rule b
]
```
YAML version:
```yaml
- id: webhook
execute-command: /home/adnan/redeploy-go-webhook.sh
command-working-directory: /home/adnan/go
pass-arguments-to-command:
- source: payload
name: head_commit.id
- source: payload
name: pusher.full_name
- source: payload
name: pusher.email
trigger-rule:
and:
- match:
type: payload-hmac-sha256
secret: mysecret
parameter:
source: header
name: X-Gitea-Signature
- match:
type: value
value: refs/heads/main
parameter:
source: payload
name: ref
```
## Slack slash command
```json
[
@@ -673,3 +725,72 @@ Webhooks feature introduced in DSM 7.x seems to be incomplete & broken, but you
}
]
```
## Incoming Azure Container Registry (ACR) webhook
ACR can send webhooks on image push events. The `hooks.json` below will handle those events and pass relevant properties as environment variables to a command.
Here is an example of a working docker webhook container used to handle the webhooks and fill the cache of a local registry: [ACR Harbor local cache feeder](https://github.com/tomdess/registry-cache-feeder).
```json
[
{
"id": "acr-push-event",
"execute-command": "/config/script-acr.sh",
"command-working-directory": "/config",
"pass-environment-to-command":
[
{
"envname": "ACTION",
"source": "payload",
"name": "action"
},
{
"envname": "REPO",
"source": "payload",
"name": "target.repository"
},
{
"envname": "TAG",
"source": "payload",
"name": "target.tag"
},
{
"envname": "DIGEST",
"source": "payload",
"name": "target.digest"
}
],
"trigger-rule":
{
"and":
[
{
"match":
{
"type": "value",
"value": "mysecretToken",
"parameter":
{
"source": "header",
"name": "X-Static-Token"
}
}
},
{
"match":
{
"type": "value",
"value": "push",
"parameter":
{
"source": "payload",
"name": "action"
}
}
}
]
}
}
]
```
+5
View File
@@ -20,6 +20,11 @@ There are four types of request values:
```
3. HTTP Request parameters
#### Valid `name` Parameters:
- `"name": "method"`
- `"name": "remote-addr"`
*Note* Anything other than above mentioned `name` parameters would be invalid!
```json
{
+33
View File
@@ -73,5 +73,38 @@ Additionally, the result is piped through the built-in Go template function `js`
```
## Template Functions
In addition to the [built-in Go template functions and features][tt], `webhook` provides the following functions:
### `getenv`
The `getenv` template function can be used for inserting environment variables into a templated configuration file.
Example:
```
"Secret": "{{getenv TEST_secret | js}}"
```
### `cat`
The `cat` template function can be used to read a file from the local filesystem. This is useful for reading secrets from files. If the file doesn't exist, it returns an empty string.
Example:
```
"secret": "{{ cat "/run/secrets/my-secret" | js }}"
```
### `credential`
The `credential` template function provides a way to retrieve secrets using [systemd's LoadCredential mechanism](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Credentials). It reads the file specified by the given name from the directory specified in the `CREDENTIALS_DIRECTORY` environment variable.
If `CREDENTIALS_DIRECTORY` is not set, it will fall back to using `getenv` to read the secret from an environment variable of the given name.
Example:
```
"secret": "{{ credential "my-secret" | js }}"
```
[w]: https://github.com/adnanh/webhook
[tt]: https://golang.org/pkg/text/template/
+32 -4
View File
@@ -13,12 +13,12 @@ import (
"errors"
"fmt"
"hash"
"io/ioutil"
"log"
"math"
"net"
"net/textproto"
"os"
"path"
"reflect"
"regexp"
"strconv"
@@ -750,14 +750,18 @@ func (h *Hooks) LoadFromFile(path string, asTemplate bool) error {
}
// parse hook file for hooks
file, e := ioutil.ReadFile(path)
file, e := os.ReadFile(path)
if e != nil {
return e
}
if asTemplate {
funcMap := template.FuncMap{"getenv": getenv}
funcMap := template.FuncMap{
"cat": cat,
"credential": credential,
"getenv": getenv,
}
tmpl, err := template.New("hooks").Funcs(funcMap).Parse(string(file))
if err != nil {
@@ -824,7 +828,7 @@ func (r Rules) Evaluate(req *Request) (bool, error) {
return r.Match.Evaluate(req)
}
return true, nil
return false, nil
}
// AndRule will evaluate to true if and only if all of the ChildRules evaluate to true
@@ -956,3 +960,27 @@ func compare(a, b string) bool {
func getenv(s string) string {
return os.Getenv(s)
}
// cat provides a template function to retrieve content of files
// Similarly to getenv, if no file is found, it returns the empty string
func cat(s string) string {
data, e := os.ReadFile(s)
if e != nil {
return ""
}
return strings.TrimSuffix(string(data), "\n")
}
// credential provides a template function to retreive secrets using systemd's LoadCredential mechanism
func credential(s string) string {
dir := getenv("CREDENTIALS_DIRECTORY")
// If no credential directory is found, fallback to the env variable
if dir == "" {
return getenv(s)
}
return cat(path.Join(dir, s))
}
+1 -2
View File
@@ -351,7 +351,6 @@ func TestHookExtractCommandArguments(t *testing.T) {
// we test both cases where the name of the data is used as the name of the
// env key & the case where the hook definition sets the env var name to a
// fixed value using the envname construct like so::
//
// [
// {
// "id": "push",
@@ -597,7 +596,7 @@ var andRuleTests = []struct {
[]byte{},
true, false,
},
{"empty rule", AndRule{{}}, nil, nil, nil, nil, true, false},
{"empty rule", AndRule{{}}, nil, nil, nil, nil, false, false},
// failures
{
"invalid rule",
+5 -5
View File
@@ -5,7 +5,7 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
@@ -25,7 +25,7 @@ import (
)
const (
version = "2.8.2"
version = "2.8.3"
)
var (
@@ -172,7 +172,7 @@ func main() {
}
if !*verbose {
log.SetOutput(io.Discard)
log.SetOutput(ioutil.Discard)
}
// Create pidfile
@@ -379,7 +379,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
isMultipart := strings.HasPrefix(req.ContentType, "multipart/form-data;")
if !isMultipart {
req.Body, err = io.ReadAll(r.Body)
req.Body, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("[%s] error reading the request body: %+v\n", req.ID, err)
}
@@ -608,7 +608,7 @@ func handleHook(h *hook.Hook, r *hook.Request) (string, error) {
}
for i := range files {
tmpfile, err := os.CreateTemp(h.CommandWorkingDirectory, files[i].EnvName)
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
if err != nil {
log.Printf("[%s] error creating temp file [%s]", r.ID, err)
continue