Compare commits

..

1 Commits

Author SHA1 Message Date
Adnan Hajdarevic c2da943240 Fix #718 2025-01-12 00:44:33 +01:00
6 changed files with 32 additions and 213 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) - [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) - [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 - [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://tmertz.com/2018/01/private-webhooks/) by [Thomas](https://tmertz.com) - [Private webhooks](https://ihateithe.re/2018/01/private-webhooks/) by [Thomas](https://ihateithe.re/colophon/)
- [Adventures in webhooks](https://medium.com/@draketech/adventures-in-webhooks-2d6584501c62) by [Drake](https://medium.com/@draketech) - [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/) - [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/) - [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/)
+6 -127
View File
@@ -22,25 +22,6 @@ although the examples on this page all use the JSON format.
* [Multipart Form Data](#multipart-form-data) * [Multipart Form Data](#multipart-form-data)
* [Pass string arguments to command](#pass-string-arguments-to-command) * [Pass string arguments to command](#pass-string-arguments-to-command)
* [Receive Synology DSM notifications](#receive-synology-notifications) * [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 ## Incoming Github webhook
@@ -232,11 +213,7 @@ Values in the request body can be accessed in the command or to the match rule b
} }
] ]
``` ```
## Incoming Gitea webhook ## Incoming Gitea webhook
JSON version:
```json ```json
[ [
{ {
@@ -251,7 +228,7 @@ JSON version:
}, },
{ {
"source": "payload", "source": "payload",
"name": "pusher.full_name" "name": "pusher.name"
}, },
{ {
"source": "payload", "source": "payload",
@@ -265,12 +242,12 @@ JSON version:
{ {
"match": "match":
{ {
"type": "payload-hmac-sha256", "type": "value",
"secret": "mysecret", "value": "mysecret",
"parameter": "parameter":
{ {
"source": "header", "source": "payload",
"name": "X-Gitea-Signature" "name": "secret"
} }
} }
}, },
@@ -278,7 +255,7 @@ JSON version:
"match": "match":
{ {
"type": "value", "type": "value",
"value": "refs/heads/main", "value": "refs/heads/master",
"parameter": "parameter":
{ {
"source": "payload", "source": "payload",
@@ -292,35 +269,6 @@ JSON version:
] ]
``` ```
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 ## Slack slash command
```json ```json
[ [
@@ -725,72 +673,3 @@ 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"
}
}
}
]
}
}
]
```
-33
View File
@@ -73,38 +73,5 @@ 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 [w]: https://github.com/adnanh/webhook
[tt]: https://golang.org/pkg/text/template/ [tt]: https://golang.org/pkg/text/template/
+4 -32
View File
@@ -13,12 +13,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
"io/ioutil"
"log" "log"
"math" "math"
"net" "net"
"net/textproto" "net/textproto"
"os" "os"
"path"
"reflect" "reflect"
"regexp" "regexp"
"strconv" "strconv"
@@ -750,18 +750,14 @@ func (h *Hooks) LoadFromFile(path string, asTemplate bool) error {
} }
// parse hook file for hooks // parse hook file for hooks
file, e := os.ReadFile(path) file, e := ioutil.ReadFile(path)
if e != nil { if e != nil {
return e return e
} }
if asTemplate { if asTemplate {
funcMap := template.FuncMap{ funcMap := template.FuncMap{"getenv": getenv}
"cat": cat,
"credential": credential,
"getenv": getenv,
}
tmpl, err := template.New("hooks").Funcs(funcMap).Parse(string(file)) tmpl, err := template.New("hooks").Funcs(funcMap).Parse(string(file))
if err != nil { if err != nil {
@@ -828,7 +824,7 @@ func (r Rules) Evaluate(req *Request) (bool, error) {
return r.Match.Evaluate(req) return r.Match.Evaluate(req)
} }
return false, nil return true, nil
} }
// AndRule will evaluate to true if and only if all of the ChildRules evaluate to true // AndRule will evaluate to true if and only if all of the ChildRules evaluate to true
@@ -960,27 +956,3 @@ func compare(a, b string) bool {
func getenv(s string) string { func getenv(s string) string {
return os.Getenv(s) 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))
}
+16 -15
View File
@@ -351,20 +351,21 @@ func TestHookExtractCommandArguments(t *testing.T) {
// we test both cases where the name of the data is used as the name of the // 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 // env key & the case where the hook definition sets the env var name to a
// fixed value using the envname construct like so:: // fixed value using the envname construct like so::
// [ //
// { // [
// "id": "push", // {
// "execute-command": "bb2mm", // "id": "push",
// "command-working-directory": "/tmp", // "execute-command": "bb2mm",
// "pass-environment-to-command": // "command-working-directory": "/tmp",
// [ // "pass-environment-to-command":
// { // [
// "source": "entire-payload", // {
// "envname": "PAYLOAD" // "source": "entire-payload",
// }, // "envname": "PAYLOAD"
// ] // },
// } // ]
// ] // }
// ]
var hookExtractCommandArgumentsForEnvTests = []struct { var hookExtractCommandArgumentsForEnvTests = []struct {
exec string exec string
args []Argument args []Argument
@@ -596,7 +597,7 @@ var andRuleTests = []struct {
[]byte{}, []byte{},
true, false, true, false,
}, },
{"empty rule", AndRule{{}}, nil, nil, nil, nil, false, false}, {"empty rule", AndRule{{}}, nil, nil, nil, nil, true, false},
// failures // failures
{ {
"invalid rule", "invalid rule",
+5 -5
View File
@@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
@@ -25,7 +25,7 @@ import (
) )
const ( const (
version = "2.8.3" version = "2.8.2"
) )
var ( var (
@@ -172,7 +172,7 @@ func main() {
} }
if !*verbose { if !*verbose {
log.SetOutput(ioutil.Discard) log.SetOutput(io.Discard)
} }
// Create pidfile // Create pidfile
@@ -379,7 +379,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
isMultipart := strings.HasPrefix(req.ContentType, "multipart/form-data;") isMultipart := strings.HasPrefix(req.ContentType, "multipart/form-data;")
if !isMultipart { if !isMultipart {
req.Body, err = ioutil.ReadAll(r.Body) req.Body, err = io.ReadAll(r.Body)
if err != nil { if err != nil {
log.Printf("[%s] error reading the request body: %+v\n", req.ID, err) 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 { for i := range files {
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName) tmpfile, err := os.CreateTemp(h.CommandWorkingDirectory, files[i].EnvName)
if err != nil { if err != nil {
log.Printf("[%s] error creating temp file [%s]", r.ID, err) log.Printf("[%s] error creating temp file [%s]", r.ID, err)
continue continue