├── Makefile ├── .drone.yml ├── Dockerfile ├── README.md ├── LICENSE └── main.go /Makefile: -------------------------------------------------------------------------------- 1 | 2 | image: 3 | docker build -t webhookrelay/webhook-dispatcher:latest -f Dockerfile . 4 | docker push webhookrelay/webhook-dispatcher:latest -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | 4 | steps: 5 | - name: build 6 | image: golang:1.11.4 7 | commands: 8 | - go build 9 | 10 | - name: slack 11 | image: plugins/slack 12 | settings: 13 | webhook: 14 | from_secret: slack_url 15 | channel: general 16 | username: drone 17 | icon_url: https://picsum.photos/256/256/?image=844 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Creates relayd daemon image 2 | 3 | FROM golang:1.11.4-alpine 4 | COPY . /go/src/github.com/webhookrelay/webhook-dispatcher 5 | WORKDIR /go/src/github.com/webhookrelay/webhook-dispatcher 6 | RUN apk add --no-cache git 7 | RUN go install --ldflags="-s -w" 8 | 9 | FROM alpine:latest 10 | RUN apk --no-cache add ca-certificates 11 | COPY --from=0 /go/bin/webhook-dispatcher /webhook-dispatcher 12 | ENTRYPOINT ["/webhook-dispatcher"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webhook Dispatcher 2 | 3 | [![Build Status](https://drone-kr.webrelay.io/api/badges/webhookrelay/webhook-dispatcher/status.svg)](https://drone-kr.webrelay.io/webhookrelay/webhook-dispatcher) 4 | 5 | Simple webhook dispatcher that can be used in the pipelines. Targeted at container workloads where native webhooks aren't that easy to get or configure (looking at you Google Cloud Builder). 6 | 7 | ## Usage 8 | 9 | ```bash 10 | $ webhook-dispatcher --destination https://my.webhookrelay.com/v1/webhooks/544a6fe8-83fe-4361-a264-0fd486e1665d --body hello 11 | {"status_code":200,"body":""} 12 | ``` 13 | 14 | Available options: 15 | 16 | ```bash 17 | webhook-dispatcher --help 18 | Usage of webhook-dispatcher: 19 | --basic-auth string 20 | Optional basic authentication in a 'user:pass' format 21 | --body string 22 | Webhook payload 23 | --destination string 24 | Webhook destination (https://example.com/webhooks) 25 | --method string 26 | Webhook method (defaults to 'POST') 27 | 28 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Webhook-Relay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | var strDestination = flag.String("destination", "", "Webhook destination (https://example.com/webhooks)") 15 | var strBody = flag.String("body", "", "Webhook payload") 16 | var strMethod = flag.String("method", "", "Webhook method (defaults to 'POST')") 17 | var strAuth = flag.String("basic-auth", "", "Optional basic authentication in a 'user:pass' format") 18 | 19 | func main() { 20 | 21 | flag.Usage = func() { 22 | fmt.Printf("Usage of %s:\n", os.Args[0]) 23 | fmt.Printf(" webhook-dispatcher --destination https://my.webhookrelay.com/v1/webhooks/.... --body hello\n") 24 | flag.PrintDefaults() 25 | } 26 | 27 | if len(os.Args) == 1 { 28 | flag.PrintDefaults() 29 | os.Exit(1) 30 | } 31 | 32 | flag.Parse() 33 | 34 | if *strDestination == "" && os.Getenv("DESTINATION") == "" { 35 | fmt.Println("both --destination flag and DESTINATION env variable cannot be empty, usage:") 36 | flag.PrintDefaults() 37 | os.Exit(1) 38 | } 39 | 40 | client := http.DefaultClient 41 | 42 | var ( 43 | user string 44 | pass string 45 | ) 46 | if *strAuth != "" { 47 | parts := strings.SplitN(*strAuth, ":", 2) 48 | if len(parts) > 0 { 49 | user = parts[0] 50 | pass = parts[1] 51 | } 52 | } 53 | 54 | method := http.MethodPost 55 | switch *strMethod { 56 | case http.MethodGet, http.MethodHead, http.MethodDelete, http.MethodPost, http.MethodPut, http.MethodPatch: 57 | method = *strMethod 58 | default: 59 | // something invalid 60 | } 61 | 62 | var destination string 63 | if *strDestination != "" { 64 | destination = *strDestination 65 | } 66 | if os.Getenv("DESTINATION") != "" { 67 | destination = os.Getenv("DESTINATION") 68 | } 69 | 70 | req, err := http.NewRequest(method, destination, bytes.NewBufferString(*strBody)) 71 | if err != nil { 72 | fmt.Printf("failed to create request: %s \n", err) 73 | os.Exit(1) 74 | } 75 | 76 | if user != "" && pass != "" { 77 | req.SetBasicAuth(user, pass) 78 | } 79 | 80 | resp, err := client.Do(req) 81 | if err != nil { 82 | fmt.Printf("request failed: %s \n", err) 83 | os.Exit(1) 84 | } 85 | defer resp.Body.Close() 86 | 87 | bodyBts, err := ioutil.ReadAll(resp.Body) 88 | if err != nil { 89 | fmt.Printf("response body read failed: %s \n", err) 90 | os.Exit(1) 91 | } 92 | 93 | var wr webhookResponse 94 | 95 | wr.Body = string(bodyBts) 96 | wr.StatusCode = resp.StatusCode 97 | 98 | encoded, err := json.Marshal(&wr) 99 | if err != nil { 100 | fmt.Printf("failed to encode response: %s \n", err) 101 | os.Exit(1) 102 | } 103 | 104 | fmt.Println(string(encoded)) 105 | os.Exit(0) 106 | } 107 | 108 | type webhookResponse struct { 109 | StatusCode int `json:"status_code"` 110 | Body string `json:"body"` 111 | } 112 | --------------------------------------------------------------------------------