Compare commits

..

19 Commits

Author SHA1 Message Date
Adnan Hajdarević 147c95dd8b Merge pull request #158 from adnanh/development
2.6.5
2017-08-09 09:53:37 +02:00
Adnan Hajdarevic cfd138c96d Fix #141 2017-08-09 09:52:59 +02:00
Adnan Hajdarević c19e514ee9 Merge pull request #143 from wrouesnel/yaml-decoding
Use the github.com/ghodss/yaml library to unserialize webhooks.
2017-08-09 09:29:58 +02:00
Adnan Hajdarević c7ec25f378 Merge pull request #156 from andrewharvey/patch-1
Update README.md for Debian stretch release of webhook package
2017-08-02 14:06:24 +02:00
Andrew Harvey 6916f89a2b Update README.md for Debian stretch release of webhook package 2017-08-02 22:02:55 +10:00
Adnan Hajdarević ed251c3cc7 Merge pull request #149 from adnanh/master
Readme backmerge
2017-07-06 09:25:12 +02:00
Adnan Hajdarević 9db13ef649 Update README.md 2017-07-06 09:24:50 +02:00
Adnan Hajdarević 98512463d8 Update README.md 2017-07-06 09:24:00 +02:00
Will Rouesnel 53ec2e7acc Add tests for YAML file parsing. 2017-06-29 02:34:58 +10:00
Adnan Hajdarević c581e0532b Merge pull request #144 from ilkelma/use-combined-output
Change to CombinedOutput to consume Stderr as well
2017-06-16 10:59:03 +02:00
ilkelma 3a75bbf4db Change to CombinedOutput to consume Stderr as well 2017-06-13 16:23:59 -04:00
Will Rouesnel 2f1ea098c0 Use the github.com/ghodss/yaml library to unserialize webhooks.
This supports both JSON and YAML seamlessly, providing for an easier human
parseable format on disk.
2017-06-12 19:56:09 +10:00
Adnan Hajdarević ccd28908db Create README.md 2017-06-08 11:16:41 +02:00
Adnan Hajdarević 69a4aec1f3 Merge pull request #139 from adnanh/development
2.6.4
2017-05-27 17:15:40 +02:00
Adnan Hajdarevic a38875213a fix tests, log ip of the incoming request, bump version 2017-05-27 17:14:38 +02:00
Adnan Hajdarević 249962f002 Merge pull request #127 from adnanh/master
Backmerge
2017-04-10 16:08:47 +02:00
Adnan Hajdarević ced9e01895 Update README.md 2017-04-10 16:08:14 +02:00
Adnan Hajdarević 5561fa3308 Merge pull request #126 from carsonip/patch-1
Add prebuilt binaries to readme.md
2017-04-09 10:37:52 +02:00
Carson Ip 47b82c7c4a Add prebuilt binaries to readme.md
Link to prebuilt binaries should be available in readme.md for users who don't have Go installed and just want things to work.
2017-04-09 01:55:59 +02:00
7 changed files with 191 additions and 62 deletions
+12 -4
View File
@@ -4,7 +4,7 @@
# Hookdoo
<a href="https://www.hookdoo.com/?github"><img src="https://my.hookdoo.com/logo/logo-dark-96.png" alt="hookdoo" align="left" /></a>
If you don't have time to waste configuring, hosting, debugging and maintaining your webhook instance, we offer a __SaaS__ solution that has all of the capabilities webhook provides, plus a lot more, and all that packaged in a nice friendly web interface. If you are interested, find out more at [hookdoo website](https://www.hookdoo.com/). If you have any questions, you can contact us at info@hookdoo.com
If you don't have time to waste configuring, hosting, debugging and maintaining your webhook instance, we offer a __SaaS__ solution that has all of the capabilities webhook provides, plus a lot more, and all that packaged in a nice friendly web interface. If you are interested, find out more at [hookdoo website](https://www.hookdoo.com/?ref=github-webhook-readme). If you have any questions, you can contact us at info@hookdoo.com
# What is webhook?
@@ -12,7 +12,7 @@ If you don't have time to waste configuring, hosting, debugging and maintaining
For example, if you're using Github or Bitbucket, you can use [webhook](https://github.com/adnanh/webhook/) to set up a hook that runs a redeploy script for your project on your staging server, whenever you push changes to the master branch of your project.
If you use Slack, you can set up an "Outgoing webhook integration" to run various commands on your server, which can then report back directly to your Slack channels using the "Incoming webhook integrations".
If you use Mattermost or Slack, you can set up an "Outgoing webhook integration" or "Slash command" to run various commands on your server, which can then report back directly to you or your channels using the "Incoming webhook integrations", or the appropriate response body.
[webhook](https://github.com/adnanh/webhook/) aims to do nothing more than it should do, and that is:
1. receive the request,
@@ -35,8 +35,11 @@ $ go get github.com/adnanh/webhook
to get the latest version of the [webhook](https://github.com/adnanh/webhook/).
### Using package manager
#### Debian "sid"
If you are using unstable version of Debian linux ("sid"), you can install webhook using `apt-get install webhook` which will install community packaged version (thanks [@freeekanayaka](https://github.com/freeekanayaka)) from https://packages.debian.org/sid/webhook
#### Debian
If you are using Debian linux ("stretch" or later), you can install webhook using `apt-get install webhook` which will install community packaged version (thanks [@freeekanayaka](https://github.com/freeekanayaka)) from https://packages.debian.org/sid/webhook
### Download prebuilt binaries
Prebuilt binaries for different architectures are available at [GitHub Releases](https://github.com/adnanh/webhook/releases).
## Configuration
Next step is to define some hooks you want [webhook](https://github.com/adnanh/webhook/) to serve. Begin by creating an empty file named `hooks.json`. This file will contain an array of hooks the [webhook](https://github.com/adnanh/webhook/) will serve. Check [Hook definition page](https://github.com/adnanh/webhook/wiki/Hook-Definition) to see the detailed description of what properties a hook can contain, and how to use them.
@@ -90,6 +93,11 @@ Big thanks to [all the current contributors](https://github.com/adnanh/webhook/g
# Community Contributions
See the [webhook-contrib][wc] repository for a collections of tools and helpers related to [webhook][w] that have been contributed by the [webhook][w] community.
# Sponsors
## <a href="https://www.browserstack.com/?ref=webhook"><img src="http://www.hajdarevic.net/browserstack.svg" alt="BrowserStack" width="250"/></a>
[BrowserStack](https://www.browserstack.com/?ref=webhook) is a cloud-based cross-browser testing tool that enables developers to test their websites across various browsers on different operating systems and mobile devices, without requiring users to install virtual machines, devices or emulators.
# License
The MIT License (MIT)
+3 -1
View File
@@ -15,6 +15,8 @@ import (
"regexp"
"strconv"
"strings"
"github.com/ghodss/yaml"
)
// Constants used to specify the parameter source
@@ -503,7 +505,7 @@ func (h *Hooks) LoadFromFile(path string) error {
return e
}
e = json.Unmarshal(file, h)
e = yaml.Unmarshal(file, h)
return e
}
+8 -7
View File
@@ -233,6 +233,7 @@ var hooksLoadFromFileTests = []struct {
ok bool
}{
{"../hooks.json.example", true},
{"../hooks.yaml.example", true},
{"", true},
// failures
{"missing.json", false},
@@ -275,18 +276,18 @@ var matchRuleTests = []struct {
ok bool
err bool
}{
{"value", "", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
{"regex", "^z", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
{"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false},
{"payload-hash-sha256", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), true, false},
{"value", "", "", "z", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"regex", "^z", "", "z", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
// failures
{"value", "", "", "X", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"regex", "^X", "", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"value", "", "2", "X", "", Argument{"header", "a", ""}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, false}, // reference invalid header
// errors
{"regex", "*", "", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
{"payload-hash-sha256", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
{"regex", "*", "", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
// IP whitelisting, valid cases
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
+28
View File
@@ -0,0 +1,28 @@
- id: webhook
execute-command: /home/adnan/redeploy-go-webhook.sh
command-working-directory: /home/adnan/go
response-message: I got the payload!
response-headers:
- name: Access-Control-Allow-Origin
value: '*'
pass-arguments-to-command:
- source: payload
name: head_commit.id
- source: payload
name: pusher.name
- source: payload
name: pusher.email
trigger-rule:
and:
- match:
type: payload-hash-sha1
secret: mysecret
parameter:
source: header
name: X-Hub-Signature
- match:
type: value
value: refs/heads/master
parameter:
source: payload
name: ref
+75
View File
@@ -0,0 +1,75 @@
- trigger-rule:
and:
- match:
parameter:
source: header
name: X-Hub-Signature
secret: mysecret
type: payload-hash-sha1
- match:
parameter:
source: payload
name: ref
type: value
value: refs/heads/master
include-command-output-in-response: true
trigger-rule-mismatch-http-response-code: 400
execute-command: '{{ .Hookecho }}'
pass-arguments-to-command:
- source: payload
name: head_commit.id
- source: payload
name: head_commit.author.email
pass-environment-to-command:
- source: payload
name: head_commit.timestamp
id: github
command-working-directory: /
- trigger-rule:
and:
- match:
parameter:
source: payload
name: payload.canon_url
type: value
value: https://bitbucket.org
- match:
parameter:
source: payload
name: payload.repository.absolute_url
type: value
value: /webhook/testing/
- match:
parameter:
source: payload
name: payload.commits.0.branch
type: value
value: master
parse-parameters-as-json:
- source: payload
name: payload
trigger-rule-mismatch-http-response-code: 999
execute-command: '{{ .Hookecho }}'
response-message: success
include-command-output-in-response: false
id: bitbucket
command-working-directory: /
- trigger-rule:
match:
parameter:
source: payload
name: ref
type: value
value: refs/heads/master
pass-arguments-to-command:
- source: payload
name: commits.0.id
- source: payload
name: user_name
- source: payload
name: user_email
execute-command: '{{ .Hookecho }}'
response-message: success
include-command-output-in-response: true
id: gitlab
command-working-directory: /
+14 -3
View File
@@ -21,7 +21,7 @@ import (
)
const (
version = "2.6.3"
version = "2.6.5"
)
var (
@@ -152,7 +152,16 @@ func main() {
}
l := negroni.NewLogger()
l.ALogger = log.New(os.Stderr, "[webhook] ", log.Ldate|log.Ltime)
l.SetFormat("{{.Status}} | {{.Duration}} | {{.Hostname}} | {{.Method}} {{.Path}} \n")
standardLogger := log.New(os.Stdout, "[webhook] ", log.Ldate|log.Ltime)
if !*verbose {
standardLogger.SetOutput(ioutil.Discard)
}
l.ALogger = standardLogger
negroniRecovery := &negroni.Recovery{
Logger: l.ALogger,
@@ -188,6 +197,8 @@ func main() {
}
func hookHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("incoming HTTP request from %s\n", r.RemoteAddr)
for _, responseHeader := range responseHeaders {
w.Header().Set(responseHeader.Name, responseHeader.Value)
}
@@ -324,7 +335,7 @@ func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, b
log.Printf("executing %s (%s) with arguments %q and environment %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
out, err := cmd.Output()
out, err := cmd.CombinedOutput()
log.Printf("command output: %s\n", out)
+51 -47
View File
@@ -21,60 +21,62 @@ func TestWebhook(t *testing.T) {
hookecho, cleanupHookecho := buildHookecho(t)
defer cleanupHookecho()
config, cleanupConfig := genConfig(t, hookecho)
defer cleanupConfig()
for _, hookTmpl := range []string{"test/hooks.json.tmpl", "test/hooks.yaml.tmpl"} {
config, cleanupConfig := genConfig(t, hookecho, hookTmpl)
defer cleanupConfig()
webhook, cleanupWebhook := buildWebhook(t)
defer cleanupWebhook()
webhook, cleanupWebhook := buildWebhook(t)
defer cleanupWebhook()
ip, port := serverAddress(t)
args := []string{fmt.Sprintf("-hooks=%s", config), fmt.Sprintf("-ip=%s", ip), fmt.Sprintf("-port=%s", port), "-verbose"}
ip, port := serverAddress(t)
args := []string{fmt.Sprintf("-hooks=%s", config), fmt.Sprintf("-ip=%s", ip), fmt.Sprintf("-port=%s", port), "-verbose"}
cmd := exec.Command(webhook, args...)
//cmd.Stderr = os.Stderr // uncomment to see verbose output
cmd.Env = webhookEnv()
cmd.Args[0] = "webhook"
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start webhook: %s", err)
}
defer killAndWait(cmd)
waitForServerReady(t, ip, port)
for _, tt := range hookHandlerTests {
url := fmt.Sprintf("http://%s:%s/hooks/%s", ip, port, tt.id)
req, err := http.NewRequest("POST", url, ioutil.NopCloser(strings.NewReader(tt.body)))
if err != nil {
t.Errorf("New request failed: %s", err)
cmd := exec.Command(webhook, args...)
//cmd.Stderr = os.Stderr // uncomment to see verbose output
cmd.Env = webhookEnv()
cmd.Args[0] = "webhook"
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start webhook: %s", err)
}
defer killAndWait(cmd)
for k, v := range tt.headers {
req.Header.Add(k, v)
}
waitForServerReady(t, ip, port)
var res *http.Response
for _, tt := range hookHandlerTests {
url := fmt.Sprintf("http://%s:%s/hooks/%s", ip, port, tt.id)
if tt.urlencoded == true {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
} else {
req.Header.Add("Content-Type", "application/json")
}
req, err := http.NewRequest("POST", url, ioutil.NopCloser(strings.NewReader(tt.body)))
if err != nil {
t.Errorf("New request failed: %s", err)
}
client := &http.Client{}
res, err = client.Do(req)
if err != nil {
t.Errorf("client.Do failed: %s", err)
}
for k, v := range tt.headers {
req.Header.Add(k, v)
}
body, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("POST %q: failed to ready body: %s", tt.desc, err)
}
var res *http.Response
if res.StatusCode != tt.respStatus || string(body) != tt.respBody {
t.Errorf("failed %q (id: %s):\nexpected status: %#v, response: %s\ngot status: %#v, response: %s", tt.desc, tt.id, tt.respStatus, tt.respBody, res.StatusCode, body)
if tt.urlencoded == true {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
} else {
req.Header.Add("Content-Type", "application/json")
}
client := &http.Client{}
res, err = client.Do(req)
if err != nil {
t.Errorf("client.Do failed: %s", err)
}
body, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("POST %q: failed to ready body: %s", tt.desc, err)
}
if res.StatusCode != tt.respStatus || string(body) != tt.respBody {
t.Errorf("failed %q (id: %s):\nexpected status: %#v, response: %s\ngot status: %#v, response: %s", tt.desc, tt.id, tt.respStatus, tt.respBody, res.StatusCode, body)
}
}
}
}
@@ -103,8 +105,8 @@ func buildHookecho(t *testing.T) (bin string, cleanup func()) {
return bin, func() { os.RemoveAll(tmp) }
}
func genConfig(t *testing.T, bin string) (config string, cleanup func()) {
tmpl := template.Must(template.ParseFiles("test/hooks.json.tmpl"))
func genConfig(t *testing.T, bin string, hookTemplate string) (config string, cleanup func()) {
tmpl := template.Must(template.ParseFiles(hookTemplate))
tmp, err := ioutil.TempDir("", "webhook-config-")
if err != nil {
@@ -116,7 +118,9 @@ func genConfig(t *testing.T, bin string) (config string, cleanup func()) {
}
}()
path := filepath.Join(tmp, "hooks.json")
outputBaseName := filepath.Ext(filepath.Ext(hookTemplate))
path := filepath.Join(tmp, outputBaseName)
file, err := os.Create(path)
if err != nil {
t.Fatalf("Creating config template: %v", err)