mirror of
https://github.com/adnanh/webhook.git
synced 2026-07-27 01:29:16 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2da943240 |
@@ -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
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -20,11 +20,6 @@ There are four types of request values:
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. HTTP Request parameters
|
3. HTTP Request parameters
|
||||||
#### Valid `name` Parameters:
|
|
||||||
- `"name": "method"`
|
|
||||||
- `"name": "remote-addr"`
|
|
||||||
|
|
||||||
*Note* Anything other than above mentioned `name` parameters would be invalid!
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
@@ -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))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ 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",
|
// "id": "push",
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user