commit
d2c35576c0
21
.travis.yml
Normal file
21
.travis.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -d -v -t ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -race ./...
|
15
appveyor.yml
Normal file
15
appveyor.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
clone_folder: C:\gopath\src\github.com\adnanh\webhook
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
|
||||||
|
install:
|
||||||
|
- set PATH=%PATH%;C:\MinGW\bin;%GOPATH%\bin
|
||||||
|
- echo %PATH%
|
||||||
|
- echo %GOPATH%
|
||||||
|
- go version
|
||||||
|
- go env
|
||||||
|
- go get -d -v -t ./...
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- go test -v -race ./...
|
@ -281,3 +281,44 @@ or in a single line, using https://github.com/jpmens/jo to generate the JSON cod
|
|||||||
jo binary=%filename.zip | curl -H "Content-Type:application/json" -X POST -d @- \
|
jo binary=%filename.zip | curl -H "Content-Type:application/json" -X POST -d @- \
|
||||||
http://localhost:9000/hooks/test-file-webhook
|
http://localhost:9000/hooks/test-file-webhook
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
|
## Incoming Scalr Webhook
|
||||||
|
[Guide by @hassanbabaie]
|
||||||
|
Scalr makes webhook calls based on an event to a configured webhook endpoint (for example Host Down, Host Up). Webhook endpoints are URLs where Scalr will deliver Webhook notifications.
|
||||||
|
Scalr assigns a unique signing key for every configured webhook endpoint.
|
||||||
|
Refer to this URL for information on how to setup the webhook call on the Scalr side: [Scalr Wiki Webhooks](https://scalr-wiki.atlassian.net/wiki/spaces/docs/pages/6193173/Webhooks)
|
||||||
|
In order to leverage the Signing Key for addtional authentication/security you must configure the trigger rule with a match type of "scalr-signature".
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "redeploy-webhook",
|
||||||
|
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
|
||||||
|
"command-working-directory": "/home/adnan/go",
|
||||||
|
"include-command-output-in-response": true,
|
||||||
|
"trigger-rule":
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "scalr-signature",
|
||||||
|
"secret": "Scalr-provided signing key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pass-environment-to-command":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"envname": "EVENT_NAME",
|
||||||
|
"source": "payload",
|
||||||
|
"name": "eventName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"envname": "SERVER_HOSTNAME",
|
||||||
|
"source": "payload",
|
||||||
|
"name": "data.SCALR_SERVER_HOSTNAME"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
```
|
@ -199,3 +199,19 @@ The IP can be IPv4- or IPv6-formatted, using [CIDR notation](https://en.wikipedi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 5. Match scalr-signature
|
||||||
|
|
||||||
|
The trigger rule checks the scalr signature and also checks that the request was signed less than 5 minutes before it was received.
|
||||||
|
A unqiue signing key is generated for each webhook endpoint URL you register in Scalr.
|
||||||
|
Given the time check make sure that NTP is enabled on both your Scalr and webhook server to prevent any issues
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "scalr-signature",
|
||||||
|
"secret": "Scalr-provided signing key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
56
hook/hook.go
56
hook/hook.go
@ -11,8 +11,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
@ -94,9 +94,7 @@ func (e *ParseError) Error() string {
|
|||||||
|
|
||||||
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
||||||
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) {
|
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) {
|
||||||
if strings.HasPrefix(signature, "sha1=") {
|
signature = strings.TrimPrefix(signature, "sha1=")
|
||||||
signature = signature[5:]
|
|
||||||
}
|
|
||||||
|
|
||||||
mac := hmac.New(sha1.New, []byte(secret))
|
mac := hmac.New(sha1.New, []byte(secret))
|
||||||
_, err := mac.Write(payload)
|
_, err := mac.Write(payload)
|
||||||
@ -113,9 +111,7 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
|
|||||||
|
|
||||||
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
||||||
func CheckPayloadSignature256(payload []byte, secret string, signature string) (string, error) {
|
func CheckPayloadSignature256(payload []byte, secret string, signature string) (string, error) {
|
||||||
if strings.HasPrefix(signature, "sha256=") {
|
signature = strings.TrimPrefix(signature, "sha256=")
|
||||||
signature = signature[7:]
|
|
||||||
}
|
|
||||||
|
|
||||||
mac := hmac.New(sha256.New, []byte(secret))
|
mac := hmac.New(sha256.New, []byte(secret))
|
||||||
_, err := mac.Write(payload)
|
_, err := mac.Write(payload)
|
||||||
@ -131,30 +127,30 @@ func CheckPayloadSignature256(payload []byte, secret string, signature string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
|
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
|
||||||
// Check for the signature and date headers
|
// Check for the signature and date headers
|
||||||
if _, ok := headers["X-Signature"]; !ok {
|
if _, ok := headers["X-Signature"]; !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if _, ok := headers["Date"]; !ok {
|
if _, ok := headers["Date"]; !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
providedSignature := headers["X-Signature"].(string)
|
providedSignature := headers["X-Signature"].(string)
|
||||||
dateHeader := headers["Date"].(string)
|
dateHeader := headers["Date"].(string)
|
||||||
mac := hmac.New(sha1.New, []byte(signingKey))
|
mac := hmac.New(sha1.New, []byte(signingKey))
|
||||||
mac.Write(body)
|
mac.Write(body)
|
||||||
mac.Write([]byte(dateHeader))
|
mac.Write([]byte(dateHeader))
|
||||||
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) {
|
if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) {
|
||||||
return false, &SignatureError{providedSignature}
|
return false, &SignatureError{providedSignature}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkDate {
|
if !checkDate {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
// Example format: Fri 08 Sep 2017 11:24:32 UTC
|
// Example format: Fri 08 Sep 2017 11:24:32 UTC
|
||||||
date, err := time.Parse("Mon 02 Jan 2006 15:04:05 MST", dateHeader)
|
date, err := time.Parse("Mon 02 Jan 2006 15:04:05 MST", dateHeader)
|
||||||
//date, err := time.Parse(time.RFC1123, dateHeader)
|
//date, err := time.Parse(time.RFC1123, dateHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -165,7 +161,7 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
|
|||||||
return false, &SignatureError{"outdated"}
|
return false, &SignatureError{"outdated"}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range
|
// CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range
|
||||||
// (in CIDR form or a single IP address).
|
// (in CIDR form or a single IP address).
|
||||||
@ -196,7 +192,7 @@ func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) {
|
|||||||
|
|
||||||
ipRange = strings.TrimSpace(ipRange)
|
ipRange = strings.TrimSpace(ipRange)
|
||||||
|
|
||||||
if strings.Index(ipRange, "/") == -1 {
|
if !strings.Contains(ipRange, "/") {
|
||||||
ipRange = ipRange + "/32"
|
ipRange = ipRange + "/32"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -687,7 +683,7 @@ func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body
|
|||||||
}
|
}
|
||||||
|
|
||||||
res = res && rv
|
res = res && rv
|
||||||
if res == false {
|
if !res {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -709,7 +705,7 @@ func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *
|
|||||||
}
|
}
|
||||||
|
|
||||||
res = res || rv
|
res = res || rv
|
||||||
if res == true {
|
if res {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -180,5 +180,29 @@
|
|||||||
"execute-command": "{{ .Hookecho }}",
|
"execute-command": "{{ .Hookecho }}",
|
||||||
"include-command-output-in-response": true,
|
"include-command-output-in-response": true,
|
||||||
"include-command-output-in-response-on-error": true
|
"include-command-output-in-response-on-error": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "static-params-ok",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"response-message": "success",
|
||||||
|
"include-command-output-in-response": true,
|
||||||
|
"pass-arguments-to-command": [
|
||||||
|
{
|
||||||
|
"source": "string",
|
||||||
|
"name": "passed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "warn-on-space",
|
||||||
|
"execute-command": "{{ .Hookecho }} foo",
|
||||||
|
"response-message": "success",
|
||||||
|
"include-command-output-in-response": true,
|
||||||
|
"pass-arguments-to-command": [
|
||||||
|
{
|
||||||
|
"source": "string",
|
||||||
|
"name": "passed"
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
- trigger-rule:
|
- id: github
|
||||||
|
trigger-rule:
|
||||||
and:
|
and:
|
||||||
- match:
|
- match:
|
||||||
parameter:
|
parameter:
|
||||||
@ -23,9 +24,10 @@
|
|||||||
pass-environment-to-command:
|
pass-environment-to-command:
|
||||||
- source: payload
|
- source: payload
|
||||||
name: head_commit.timestamp
|
name: head_commit.timestamp
|
||||||
id: github
|
|
||||||
command-working-directory: /
|
command-working-directory: /
|
||||||
- trigger-rule:
|
|
||||||
|
- id: bitbucket
|
||||||
|
trigger-rule:
|
||||||
and:
|
and:
|
||||||
- match:
|
- match:
|
||||||
parameter:
|
parameter:
|
||||||
@ -52,9 +54,10 @@
|
|||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
response-message: success
|
response-message: success
|
||||||
include-command-output-in-response: false
|
include-command-output-in-response: false
|
||||||
id: bitbucket
|
|
||||||
command-working-directory: /
|
command-working-directory: /
|
||||||
- trigger-rule:
|
|
||||||
|
- id: gitlab
|
||||||
|
trigger-rule:
|
||||||
match:
|
match:
|
||||||
parameter:
|
parameter:
|
||||||
source: payload
|
source: payload
|
||||||
@ -71,25 +74,28 @@
|
|||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
response-message: success
|
response-message: success
|
||||||
include-command-output-in-response: true
|
include-command-output-in-response: true
|
||||||
id: gitlab
|
|
||||||
command-working-directory: /
|
command-working-directory: /
|
||||||
|
|
||||||
- id: capture-command-output-on-success-not-by-default
|
- id: capture-command-output-on-success-not-by-default
|
||||||
pass-arguments-to-command:
|
pass-arguments-to-command:
|
||||||
- source: string
|
- source: string
|
||||||
name: exit=0
|
name: exit=0
|
||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
|
||||||
- id: capture-command-output-on-success-yes-with-flag
|
- id: capture-command-output-on-success-yes-with-flag
|
||||||
pass-arguments-to-command:
|
pass-arguments-to-command:
|
||||||
- source: string
|
- source: string
|
||||||
name: exit=0
|
name: exit=0
|
||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
include-command-output-in-response: true
|
include-command-output-in-response: true
|
||||||
|
|
||||||
- id: capture-command-output-on-error-not-by-default
|
- id: capture-command-output-on-error-not-by-default
|
||||||
pass-arguments-to-command:
|
pass-arguments-to-command:
|
||||||
- source: string
|
- source: string
|
||||||
name: exit=1
|
name: exit=1
|
||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
include-command-output-in-response: true
|
include-command-output-in-response: true
|
||||||
|
|
||||||
- id: capture-command-output-on-error-yes-with-extra-flag
|
- id: capture-command-output-on-error-yes-with-extra-flag
|
||||||
pass-arguments-to-command:
|
pass-arguments-to-command:
|
||||||
- source: string
|
- source: string
|
||||||
@ -97,3 +103,14 @@
|
|||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
include-command-output-in-response: true
|
include-command-output-in-response: true
|
||||||
include-command-output-in-response-on-error: true
|
include-command-output-in-response-on-error: true
|
||||||
|
|
||||||
|
- id: static-params-ok
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
include-command-output-in-response: true
|
||||||
|
pass-arguments-to-command:
|
||||||
|
- source: string
|
||||||
|
name: passed
|
||||||
|
|
||||||
|
- id: warn-on-space
|
||||||
|
execute-command: '{{ .Hookecho }} foo'
|
||||||
|
include-command-output-in-response: true
|
46
webhook.go
46
webhook.go
@ -23,7 +23,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "2.6.7"
|
version = "2.6.8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -120,7 +120,7 @@ func main() {
|
|||||||
|
|
||||||
newHooksFiles := hooksFiles[:0]
|
newHooksFiles := hooksFiles[:0]
|
||||||
for _, filePath := range hooksFiles {
|
for _, filePath := range hooksFiles {
|
||||||
if _, ok := loadedHooksFromFiles[filePath]; ok == true {
|
if _, ok := loadedHooksFromFiles[filePath]; ok {
|
||||||
newHooksFiles = append(newHooksFiles, filePath)
|
newHooksFiles = append(newHooksFiles, filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,10 +250,9 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle hook
|
// handle hook
|
||||||
if errors := matchedHook.ParseJSONParameters(&headers, &query, &payload); errors != nil {
|
errors := matchedHook.ParseJSONParameters(&headers, &query, &payload)
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error parsing JSON parameters: %s\n", rid, err)
|
log.Printf("[%s] error parsing JSON parameters: %s\n", rid, err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
@ -264,7 +263,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &body, r.RemoteAddr)
|
ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &body, r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("[%s] error evaluating hook: %s", rid, err)
|
msg := fmt.Sprintf("[%s] error evaluating hook: %s", rid, err)
|
||||||
log.Printf(msg)
|
log.Print(msg)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprintf(w, "Error occurred while evaluating hook rules.")
|
fmt.Fprintf(w, "Error occurred while evaluating hook rules.")
|
||||||
return
|
return
|
||||||
@ -341,40 +340,37 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
|
|||||||
cmd.Dir = h.CommandWorkingDirectory
|
cmd.Dir = h.CommandWorkingDirectory
|
||||||
|
|
||||||
cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload)
|
cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload)
|
||||||
if errors != nil {
|
for _, err := range errors {
|
||||||
for _, err := range errors {
|
log.Printf("[%s] error extracting command arguments: %s\n", rid, err)
|
||||||
log.Printf("[%s] error extracting command arguments: %s\n", rid, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var envs []string
|
var envs []string
|
||||||
envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload)
|
envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload)
|
||||||
|
|
||||||
if errors != nil {
|
for _, err := range errors {
|
||||||
for _, err := range errors {
|
log.Printf("[%s] error extracting command arguments for environment: %s\n", rid, err)
|
||||||
log.Printf("[%s] error extracting command arguments for environment: %s\n", rid, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
files, errors := h.ExtractCommandArgumentsForFile(headers, query, payload)
|
files, errors := h.ExtractCommandArgumentsForFile(headers, query, payload)
|
||||||
|
|
||||||
if errors != nil {
|
for _, err := range errors {
|
||||||
for _, err := range errors {
|
log.Printf("[%s] error extracting command arguments for file: %s\n", rid, err)
|
||||||
log.Printf("[%s] error extracting command arguments for file: %s\n", rid, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range files {
|
for i := range files {
|
||||||
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
|
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error creating temp file [%s]", rid, err)
|
log.Printf("[%s] error creating temp file [%s]", rid, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("[%s] writing env %s file %s", rid, files[i].EnvName, tmpfile.Name())
|
log.Printf("[%s] writing env %s file %s", rid, files[i].EnvName, tmpfile.Name())
|
||||||
if _, err := tmpfile.Write(files[i].Data); err != nil {
|
if _, err := tmpfile.Write(files[i].Data); err != nil {
|
||||||
log.Printf("[%s] error writing file %s [%s]", rid, tmpfile.Name(), err)
|
log.Printf("[%s] error writing file %s [%s]", rid, tmpfile.Name(), err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if err := tmpfile.Close(); err != nil {
|
if err := tmpfile.Close(); err != nil {
|
||||||
log.Printf("[%s] error closing file %s [%s]", rid, tmpfile.Name(), err)
|
log.Printf("[%s] error closing file %s [%s]", rid, tmpfile.Name(), err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
files[i].File = tmpfile
|
files[i].File = tmpfile
|
||||||
@ -394,10 +390,12 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range files {
|
for i := range files {
|
||||||
log.Printf("[%s] removing file %s\n", rid, files[i].File.Name())
|
if files[i].File != nil {
|
||||||
err := os.Remove(files[i].File.Name())
|
log.Printf("[%s] removing file %s\n", rid, files[i].File.Name())
|
||||||
if err != nil {
|
err := os.Remove(files[i].File.Name())
|
||||||
log.Printf("[%s] error removing file %s [%s]", rid, files[i].File.Name(), err)
|
if err != nil {
|
||||||
|
log.Printf("[%s] error removing file %s [%s]", rid, files[i].File.Name(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,7 +429,7 @@ func reloadHooks(hooksFilePath string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchLoadedHook(hook.ID) != nil && !wasHookIDAlreadyLoaded) || seenHooksIds[hook.ID] == true {
|
if (matchLoadedHook(hook.ID) != nil && !wasHookIDAlreadyLoaded) || seenHooksIds[hook.ID] {
|
||||||
log.Printf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!", hook.ID)
|
log.Printf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!", hook.ID)
|
||||||
log.Println("reverting hooks back to the previous configuration")
|
log.Println("reverting hooks back to the previous configuration")
|
||||||
return
|
return
|
||||||
|
281
webhook_test.go
281
webhook_test.go
@ -13,6 +13,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
@ -21,15 +22,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestStaticParams(t *testing.T) {
|
func TestStaticParams(t *testing.T) {
|
||||||
|
// FIXME(moorereason): incorporate this test into TestWebhook.
|
||||||
|
// Need to be able to execute a binary with a space in the filename.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("Skipping on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
spHeaders := make(map[string]interface{})
|
spHeaders := make(map[string]interface{})
|
||||||
spHeaders["User-Agent"] = "curl/7.54.0"
|
spHeaders["User-Agent"] = "curl/7.54.0"
|
||||||
spHeaders["Accept"] = "*/*"
|
spHeaders["Accept"] = "*/*"
|
||||||
|
|
||||||
// case 1: correct entry
|
// case 2: binary with spaces in its name
|
||||||
|
err := os.Symlink("/bin/echo", "/tmp/with space")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove("/tmp/with space")
|
||||||
|
|
||||||
spHook := &hook.Hook{
|
spHook := &hook.Hook{
|
||||||
ID: "static-params-ok",
|
ID: "static-params-name-space",
|
||||||
ExecuteCommand: "/bin/echo",
|
ExecuteCommand: "/tmp/with space",
|
||||||
CommandWorkingDirectory: "/tmp",
|
CommandWorkingDirectory: "/tmp",
|
||||||
ResponseMessage: "success",
|
ResponseMessage: "success",
|
||||||
CaptureCommandOutput: true,
|
CaptureCommandOutput: true,
|
||||||
@ -41,159 +53,126 @@ func TestStaticParams(t *testing.T) {
|
|||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
log.SetOutput(b)
|
log.SetOutput(b)
|
||||||
|
|
||||||
s, err := handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
|
_, err = handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v\n", err)
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
}
|
}
|
||||||
matched, _ := regexp.MatchString("(?s).*command output: passed.*static-params-ok", b.String())
|
matched, _ := regexp.MatchString("(?s)command output: .*static-params-name-space", b.String())
|
||||||
if !matched {
|
|
||||||
t.Fatalf("Unexpected log output:\n%s", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: binary with spaces in its name
|
|
||||||
err = os.Symlink("/bin/echo", "/tmp/with space")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
defer os.Remove("/tmp/with space")
|
|
||||||
|
|
||||||
spHook = &hook.Hook{
|
|
||||||
ID: "static-params-name-space",
|
|
||||||
ExecuteCommand: "/tmp/with space",
|
|
||||||
CommandWorkingDirectory: "/tmp",
|
|
||||||
ResponseMessage: "success",
|
|
||||||
CaptureCommandOutput: true,
|
|
||||||
PassArgumentsToCommand: []hook.Argument{
|
|
||||||
hook.Argument{Source: "string", Name: "passed"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
log.SetOutput(b)
|
|
||||||
|
|
||||||
s, err = handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v\n", err)
|
|
||||||
}
|
|
||||||
matched, _ = regexp.MatchString("(?s)command output: .*static-params-name-space", b.String())
|
|
||||||
if !matched {
|
if !matched {
|
||||||
t.Fatalf("Unexpected log output:\n%sn", b)
|
t.Fatalf("Unexpected log output:\n%sn", b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 3: parameters specified in execute-command
|
|
||||||
spHook = &hook.Hook{
|
|
||||||
ID: "static-params-bad",
|
|
||||||
ExecuteCommand: "/bin/echo success",
|
|
||||||
CommandWorkingDirectory: "/tmp",
|
|
||||||
ResponseMessage: "success",
|
|
||||||
CaptureCommandOutput: true,
|
|
||||||
PassArgumentsToCommand: []hook.Argument{
|
|
||||||
hook.Argument{Source: "string", Name: "failed"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
log.SetOutput(b)
|
|
||||||
|
|
||||||
s, err = handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Error expected, but none returned: %s\n", s)
|
|
||||||
}
|
|
||||||
matched, _ = regexp.MatchString("(?s)unable to locate command: ..bin.echo success.*use 'pass-arguments-to-command'", b.String())
|
|
||||||
if !matched {
|
|
||||||
t.Fatalf("Unexpected log output:\n%s\n", b)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebhook(t *testing.T) {
|
func TestWebhook(t *testing.T) {
|
||||||
hookecho, cleanupHookecho := buildHookecho(t)
|
hookecho, cleanupHookecho := buildHookecho(t)
|
||||||
defer cleanupHookecho()
|
defer cleanupHookecho()
|
||||||
|
|
||||||
|
webhook, cleanupWebhookFn := buildWebhook(t)
|
||||||
|
defer cleanupWebhookFn()
|
||||||
|
|
||||||
for _, hookTmpl := range []string{"test/hooks.json.tmpl", "test/hooks.yaml.tmpl"} {
|
for _, hookTmpl := range []string{"test/hooks.json.tmpl", "test/hooks.yaml.tmpl"} {
|
||||||
config, cleanupConfig := genConfig(t, hookecho, hookTmpl)
|
configPath, cleanupConfigFn := genConfig(t, hookecho, hookTmpl)
|
||||||
defer cleanupConfig()
|
defer cleanupConfigFn()
|
||||||
|
|
||||||
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"}
|
|
||||||
|
|
||||||
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 {
|
for _, tt := range hookHandlerTests {
|
||||||
url := fmt.Sprintf("http://%s:%s/hooks/%s", ip, port, tt.id)
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", url, ioutil.NopCloser(strings.NewReader(tt.body)))
|
ip, port := serverAddress(t)
|
||||||
if err != nil {
|
args := []string{fmt.Sprintf("-hooks=%s", configPath), fmt.Sprintf("-ip=%s", ip), fmt.Sprintf("-port=%s", port), "-verbose"}
|
||||||
t.Errorf("New request failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range tt.headers {
|
// Setup a buffer for capturing webhook logs for later evaluation
|
||||||
req.Header.Add(k, v)
|
b := &buffer{}
|
||||||
}
|
|
||||||
|
|
||||||
var res *http.Response
|
cmd := exec.Command(webhook, args...)
|
||||||
|
cmd.Stderr = b
|
||||||
|
cmd.Env = webhookEnv()
|
||||||
|
cmd.Args[0] = "webhook"
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
t.Fatalf("failed to start webhook: %s", err)
|
||||||
|
}
|
||||||
|
defer killAndWait(cmd)
|
||||||
|
|
||||||
if tt.urlencoded == true {
|
waitForServerReady(t, ip, port)
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
} else {
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{}
|
url := fmt.Sprintf("http://%s:%s/hooks/%s", ip, port, tt.id)
|
||||||
res, err = client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("client.Do failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
req, err := http.NewRequest("POST", url, ioutil.NopCloser(strings.NewReader(tt.body)))
|
||||||
res.Body.Close()
|
if err != nil {
|
||||||
if err != nil {
|
t.Errorf("New request failed: %s", err)
|
||||||
t.Errorf("POST %q: failed to ready body: %s", tt.desc, err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if res.StatusCode != tt.respStatus || string(body) != tt.respBody {
|
for k, v := range tt.headers {
|
||||||
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)
|
req.Header.Add(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var res *http.Response
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.logMatch == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's the potential for a race condition below where we
|
||||||
|
// try to read the logs buffer b before the logs have been
|
||||||
|
// flushed by the webhook process. Kill the process to flush
|
||||||
|
// the logs.
|
||||||
|
killAndWait(cmd)
|
||||||
|
|
||||||
|
matched, _ := regexp.MatchString(tt.logMatch, b.String())
|
||||||
|
if !matched {
|
||||||
|
t.Errorf("failed log match for %q (id: %s):\nmatch pattern: %q\ngot:\n%s", tt.desc, tt.id, tt.logMatch, b)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildHookecho(t *testing.T) (bin string, cleanup func()) {
|
func buildHookecho(t *testing.T) (binPath string, cleanupFn func()) {
|
||||||
tmp, err := ioutil.TempDir("", "hookecho-test-")
|
tmp, err := ioutil.TempDir("", "hookecho-test-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if cleanup == nil {
|
if cleanupFn == nil {
|
||||||
os.RemoveAll(tmp)
|
os.RemoveAll(tmp)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
bin = filepath.Join(tmp, "hookecho")
|
binPath = filepath.Join(tmp, "hookecho")
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
bin += ".exe"
|
binPath += ".exe"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("go", "build", "-o", bin, "test/hookecho.go")
|
cmd := exec.Command("go", "build", "-o", binPath, "test/hookecho.go")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("Building hookecho: %v", err)
|
t.Fatalf("Building hookecho: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bin, func() { os.RemoveAll(tmp) }
|
return binPath, func() { os.RemoveAll(tmp) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func genConfig(t *testing.T, bin string, hookTemplate string) (config string, cleanup func()) {
|
func genConfig(t *testing.T, bin string, hookTemplate string) (configPath string, cleanupFn func()) {
|
||||||
tmpl := template.Must(template.ParseFiles(hookTemplate))
|
tmpl := template.Must(template.ParseFiles(hookTemplate))
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "webhook-config-")
|
tmp, err := ioutil.TempDir("", "webhook-config-")
|
||||||
@ -201,7 +180,7 @@ func genConfig(t *testing.T, bin string, hookTemplate string) (config string, cl
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if cleanup == nil {
|
if cleanupFn == nil {
|
||||||
os.RemoveAll(tmp)
|
os.RemoveAll(tmp)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -215,7 +194,11 @@ func genConfig(t *testing.T, bin string, hookTemplate string) (config string, cl
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
data := struct{ Hookecho string }{filepath.ToSlash(bin)}
|
data := struct{ Hookecho string }{filepath.FromSlash(bin)}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Simulate escaped backslashes on Windows.
|
||||||
|
data.Hookecho = strings.Replace(data.Hookecho, `\`, `\\`, -1)
|
||||||
|
}
|
||||||
if err := tmpl.Execute(file, data); err != nil {
|
if err := tmpl.Execute(file, data); err != nil {
|
||||||
t.Fatalf("Executing template: %v", err)
|
t.Fatalf("Executing template: %v", err)
|
||||||
}
|
}
|
||||||
@ -223,28 +206,28 @@ func genConfig(t *testing.T, bin string, hookTemplate string) (config string, cl
|
|||||||
return path, func() { os.RemoveAll(tmp) }
|
return path, func() { os.RemoveAll(tmp) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildWebhook(t *testing.T) (bin string, cleanup func()) {
|
func buildWebhook(t *testing.T) (binPath string, cleanupFn func()) {
|
||||||
tmp, err := ioutil.TempDir("", "webhook-test-")
|
tmp, err := ioutil.TempDir("", "webhook-test-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if cleanup == nil {
|
if cleanupFn == nil {
|
||||||
os.RemoveAll(tmp)
|
os.RemoveAll(tmp)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
bin = filepath.Join(tmp, "webhook")
|
binPath = filepath.Join(tmp, "webhook")
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
bin += ".exe"
|
binPath += ".exe"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("go", "build", "-o", bin)
|
cmd := exec.Command("go", "build", "-o", binPath)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("Building webhook: %v", err)
|
t.Fatalf("Building webhook: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bin, func() { os.RemoveAll(tmp) }
|
return binPath, func() { os.RemoveAll(tmp) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverAddress(t *testing.T) (string, string) {
|
func serverAddress(t *testing.T) (string, string) {
|
||||||
@ -288,6 +271,10 @@ func waitForServer(t *testing.T, url string, status int, timeout time.Duration)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func killAndWait(cmd *exec.Cmd) {
|
func killAndWait(cmd *exec.Cmd) {
|
||||||
|
if cmd == nil || cmd.ProcessState != nil && cmd.ProcessState.Exited() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cmd.Process.Kill()
|
cmd.Process.Kill()
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
}
|
}
|
||||||
@ -313,6 +300,7 @@ var hookHandlerTests = []struct {
|
|||||||
|
|
||||||
respStatus int
|
respStatus int
|
||||||
respBody string
|
respBody string
|
||||||
|
logMatch string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"github",
|
"github",
|
||||||
@ -467,6 +455,7 @@ var hookHandlerTests = []struct {
|
|||||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||||
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
||||||
`,
|
`,
|
||||||
|
``,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bitbucket", // bitbucket sends their payload using uriencoded params.
|
"bitbucket", // bitbucket sends their payload using uriencoded params.
|
||||||
@ -476,6 +465,7 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
true,
|
true,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`success`,
|
`success`,
|
||||||
|
``,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"gitlab",
|
"gitlab",
|
||||||
@ -527,6 +517,7 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`arg: b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327 John Smith john@example.com
|
`arg: b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327 John Smith john@example.com
|
||||||
`,
|
`,
|
||||||
|
``,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -567,6 +558,7 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||||
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
||||||
`,
|
`,
|
||||||
|
``,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -605,20 +597,55 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||||
`,
|
`,
|
||||||
|
``,
|
||||||
},
|
},
|
||||||
|
|
||||||
// test with custom return code
|
// test with custom return code
|
||||||
{"empty payload", "github", nil, `{}`, false, http.StatusBadRequest, `Hook rules were not satisfied.`},
|
{"empty payload", "github", nil, `{}`, false, http.StatusBadRequest, `Hook rules were not satisfied.`, ``},
|
||||||
// test with custom invalid http code, should default to 200 OK
|
// test with custom invalid http code, should default to 200 OK
|
||||||
{"empty payload", "bitbucket", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`},
|
{"empty payload", "bitbucket", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`, ``},
|
||||||
// test with no configured http return code, should default to 200 OK
|
// test with no configured http return code, should default to 200 OK
|
||||||
{"empty payload", "gitlab", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`},
|
{"empty payload", "gitlab", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`, ``},
|
||||||
|
|
||||||
// test capturing command output
|
// test capturing command output
|
||||||
{"don't capture output on success by default", "capture-command-output-on-success-not-by-default", nil, `{}`, false, http.StatusOK, ``},
|
{"don't capture output on success by default", "capture-command-output-on-success-not-by-default", nil, `{}`, false, http.StatusOK, ``, ``},
|
||||||
{"capture output on success with flag set", "capture-command-output-on-success-yes-with-flag", nil, `{}`, false, http.StatusOK, `arg: exit=0
|
{"capture output on success with flag set", "capture-command-output-on-success-yes-with-flag", nil, `{}`, false, http.StatusOK, `arg: exit=0
|
||||||
`},
|
`, ``},
|
||||||
{"don't capture output on error by default", "capture-command-output-on-error-not-by-default", nil, `{}`, false, http.StatusInternalServerError, `Error occurred while executing the hook's command. Please check your logs for more details.`},
|
{"don't capture output on error by default", "capture-command-output-on-error-not-by-default", nil, `{}`, false, http.StatusInternalServerError, `Error occurred while executing the hook's command. Please check your logs for more details.`, ``},
|
||||||
{"capture output on error with extra flag set", "capture-command-output-on-error-yes-with-extra-flag", nil, `{}`, false, http.StatusInternalServerError, `arg: exit=1
|
{"capture output on error with extra flag set", "capture-command-output-on-error-yes-with-extra-flag", nil, `{}`, false, http.StatusInternalServerError, `arg: exit=1
|
||||||
`},
|
`, ``},
|
||||||
|
|
||||||
|
// Check logs
|
||||||
|
{"static params should pass", "static-params-ok", nil, `{}`, false, http.StatusOK, "arg: passed\n", `(?s)command output: arg: passed`},
|
||||||
|
{"command with space logs warning", "warn-on-space", nil, `{}`, false, http.StatusInternalServerError, "Error occurred while executing the hook's command. Please check your logs for more details.", `(?s)unable to locate command.*use 'pass[-]arguments[-]to[-]command' to specify args`},
|
||||||
|
}
|
||||||
|
|
||||||
|
// buffer provides a concurrency-safe bytes.Buffer to tests above.
|
||||||
|
type buffer struct {
|
||||||
|
b bytes.Buffer
|
||||||
|
m sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *buffer) Read(p []byte) (n int, err error) {
|
||||||
|
b.m.Lock()
|
||||||
|
defer b.m.Unlock()
|
||||||
|
return b.b.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *buffer) Write(p []byte) (n int, err error) {
|
||||||
|
b.m.Lock()
|
||||||
|
defer b.m.Unlock()
|
||||||
|
return b.b.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *buffer) String() string {
|
||||||
|
b.m.Lock()
|
||||||
|
defer b.m.Unlock()
|
||||||
|
return b.b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *buffer) Reset() {
|
||||||
|
b.m.Lock()
|
||||||
|
defer b.m.Unlock()
|
||||||
|
b.b.Reset()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user