mirror of
https://github.com/ncarlier/webhookd.git
synced 2025-04-09 19:53:56 +00:00
feat: Big refactoring.
Change packaging. Add compressed logs in attachment of the notification.
This commit is contained in:
parent
d913e92d5a
commit
eb4b9ba7ff
22
Makefile
22
Makefile
|
@ -4,25 +4,31 @@
|
|||
TAG:=`git describe --abbrev=0 --tags`
|
||||
LDFLAGS:=-X main.buildVersion $(TAG)
|
||||
APPNAME:=webhookd
|
||||
ROOTPKG:=github.com/ncarlier
|
||||
PKGDIR:=$(GOPATH)/src/$(ROOTPKG)
|
||||
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
prepare:
|
||||
rm -rf $(PKGDIR)
|
||||
mkdir -p $(PKGDIR)
|
||||
ln -s $(PWD)/src $(PKGDIR)/$(APPNAME)
|
||||
|
||||
build: prepare
|
||||
echo "Building $(APPNAME)..."
|
||||
go build -ldflags "$(LDFLAGS)" -o bin/$(APPNAME) ./src
|
||||
|
||||
clean: clean-dist
|
||||
rm -rf bin
|
||||
rm -f bin/$(APPNAME)
|
||||
|
||||
clean-dist:
|
||||
rm -rf dist
|
||||
|
||||
dist: clean-dist
|
||||
mkdir -p dist/linux/amd64 && GOOS=linux GOARCH=amd64 go build -o dist/linux/amd64/$(APPNAME) ./src
|
||||
# mkdir -p dist/linux/i386 && GOOS=linux GOARCH=386 go build -o dist/linux/i386/$(APPNAME) ./src
|
||||
|
||||
release: dist
|
||||
# godep restore
|
||||
tar -cvzf $(APPNAME)-linux-amd64-$(TAG).tar.gz -C dist/linux/amd64 $(APPNAME)
|
||||
# tar -cvzf $(APPNAME)-linux-i386-i386$(TAG).tar.gz -C dist/linux/i386 $(APPNAME)
|
||||
mkdir -p dist/linux/amd64 && GOOS=linux GOARCH=amd64 go build -o dist/linux/amd64/$(APPNAME) ./src
|
||||
tar -cvzf dist/$(APPNAME)-linux-amd64-$(TAG).tar.gz -C dist/linux/amd64 $(APPNAME)
|
||||
# mkdir -p dist/linux/i386 && GOOS=linux GOARCH=386 go build -o dist/linux/i386/$(APPNAME) ./src
|
||||
# tar -cvzf dist/$(APPNAME)-linux-i386-i386$(TAG).tar.gz -C dist/linux/i386 $(APPNAME)
|
||||
|
||||
|
|
20
src/hook/bitbucket_hook.go
Normal file
20
src/hook/bitbucket_hook.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type BitbucketRecord struct {
|
||||
Repository struct {
|
||||
Slug string `json:"slug"`
|
||||
Owner string `json:"owner"`
|
||||
} `json:"repository"`
|
||||
}
|
||||
|
||||
func (r BitbucketRecord) GetURL() string {
|
||||
return fmt.Sprintf("git@bitbucket.org:%s/%s.git", r.Repository.Owner, r.Repository.Owner)
|
||||
}
|
||||
|
||||
func (r BitbucketRecord) GetName() string {
|
||||
return r.Repository.Slug
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package hooks
|
||||
package hook
|
||||
|
||||
type DockerRecord struct {
|
||||
Repository struct {
|
|
@ -1,4 +1,4 @@
|
|||
package hooks
|
||||
package hook
|
||||
|
||||
type GithubRecord struct {
|
||||
Repository struct {
|
|
@ -1,4 +1,4 @@
|
|||
package hooks
|
||||
package hook
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -1,23 +0,0 @@
|
|||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type BitbucketRecord struct {
|
||||
Repository struct {
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"absolute_url"`
|
||||
} `json:"repository"`
|
||||
BaseURL string `json:"canon_url"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
func (r BitbucketRecord) GetURL() string {
|
||||
return fmt.Sprintf("%s%s", r.BaseURL, r.Repository.URL)
|
||||
}
|
||||
|
||||
func (r BitbucketRecord) GetName() string {
|
||||
return r.Repository.Name
|
||||
}
|
89
src/main.go
89
src/main.go
|
@ -1,89 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"./hooks"
|
||||
"./notifications"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
laddr = flag.String("l", ":8080", "HTTP service address (e.g.address, ':8080')")
|
||||
)
|
||||
|
||||
type HookContext struct {
|
||||
Hook string
|
||||
Action string
|
||||
args []string
|
||||
}
|
||||
|
||||
func Notify(text string, context *HookContext) {
|
||||
var subject = fmt.Sprintf("Action %s executed.", context.Action)
|
||||
var notifier, err = notifications.NotifierFactory()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if notifier == nil {
|
||||
log.Println("Notification provider not found.")
|
||||
return
|
||||
}
|
||||
notifier.Notify(text, subject)
|
||||
}
|
||||
|
||||
func RunScript(w http.ResponseWriter, context *HookContext) {
|
||||
scriptname := fmt.Sprintf("./scripts/%s/%s.sh", context.Hook, context.Action)
|
||||
log.Println("Exec script: ", scriptname)
|
||||
|
||||
out, err := exec.Command(scriptname, context.args...).Output()
|
||||
if err != nil {
|
||||
Notify(err.Error(), context)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
Notify(fmt.Sprintf("%s", out), context)
|
||||
fmt.Fprintf(w, "Action '%s' executed!", context.Action)
|
||||
}
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
context := new(HookContext)
|
||||
context.Hook = params["hookname"]
|
||||
context.Action = params["action"]
|
||||
|
||||
log.Println("Hook name: ", context.Hook)
|
||||
|
||||
var record, err = hooks.RecordFactory(context.Hook)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&record)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
context.args = []string{record.GetURL(), record.GetName()}
|
||||
|
||||
RunScript(w, context)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
rtr := mux.NewRouter()
|
||||
rtr.HandleFunc("/{hookname:[a-z]+}/{action:[a-z]+}", Handler).Methods("POST")
|
||||
|
||||
http.Handle("/", rtr)
|
||||
|
||||
log.Println("webhookd server listening...")
|
||||
log.Fatal(http.ListenAndServe(*laddr, nil))
|
||||
}
|
100
src/notification/http_notifier.go
Normal file
100
src/notification/http_notifier.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type HttpNotifier struct {
|
||||
URL string
|
||||
From string
|
||||
To string
|
||||
}
|
||||
|
||||
func NewHttpNotifier() *HttpNotifier {
|
||||
notifier := new(HttpNotifier)
|
||||
notifier.URL = os.Getenv("APP_HTTP_NOTIFIER_URL")
|
||||
if notifier.URL == "" {
|
||||
log.Println("Unable to create HTTP notifier. APP_HTTP_NOTIFIER_URL not set.")
|
||||
return nil
|
||||
}
|
||||
notifier.From = os.Getenv("APP_NOTIFIER_FROM")
|
||||
if notifier.From == "" {
|
||||
notifier.From = "webhookd <noreply@nunux.org>"
|
||||
}
|
||||
notifier.To = os.Getenv("APP_NOTIFIER_TO")
|
||||
if notifier.To == "" {
|
||||
notifier.To = "hostmaster@nunux.org"
|
||||
}
|
||||
return notifier
|
||||
}
|
||||
|
||||
func (n HttpNotifier) Notify(subject string, text string, attachfile string) {
|
||||
log.Println("HTTP notification: ", subject)
|
||||
data := make(url.Values)
|
||||
data.Set("from", n.From)
|
||||
data.Set("to", n.To)
|
||||
data.Set("subject", subject)
|
||||
data.Set("text", text)
|
||||
|
||||
if attachfile != "" {
|
||||
file, err := os.Open(attachfile)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
part, err := writer.CreateFormFile("attachment", filepath.Base(attachfile))
|
||||
if err != nil {
|
||||
log.Println("Unable to create for file", err)
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(part, file)
|
||||
|
||||
for key, val := range data {
|
||||
_ = writer.WriteField(key, val[0])
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
log.Println("Unable to close writer", err)
|
||||
return
|
||||
}
|
||||
req, err := http.NewRequest("POST", n.URL, body)
|
||||
if err != nil {
|
||||
log.Println("Unable to post request", err)
|
||||
}
|
||||
defer req.Body.Close()
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
// Submit the request
|
||||
client := &http.Client{}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Println("Unable to do the request", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check the response
|
||||
if res.StatusCode != http.StatusOK {
|
||||
log.Println("bad status: %s", res.Status)
|
||||
return
|
||||
}
|
||||
log.Println("HTTP notification done with attachment: ", attachfile)
|
||||
} else {
|
||||
resp, err := http.PostForm(n.URL, data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
log.Println("HTTP notification done")
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package notifications
|
||||
package notification
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
type Notifier interface {
|
||||
Notify(text string, subject string)
|
||||
Notify(subject string, text string, attachfile string)
|
||||
}
|
||||
|
||||
func NotifierFactory() (Notifier, error) {
|
|
@ -1,4 +1,4 @@
|
|||
package notifications
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -30,7 +30,7 @@ func NewSmtpNotifier() *SmtpNotifier {
|
|||
return notifier
|
||||
}
|
||||
|
||||
func (n SmtpNotifier) Notify(text string, subject string) {
|
||||
func (n SmtpNotifier) Notify(subject string, text string, attachfile string) {
|
||||
log.Println("SMTP notification: ", subject)
|
||||
// Connect to the remote SMTP server.
|
||||
c, err := smtp.Dial(n.Host)
|
|
@ -1,48 +0,0 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
type HttpNotifier struct {
|
||||
URL string
|
||||
From string
|
||||
To string
|
||||
}
|
||||
|
||||
func NewHttpNotifier() *HttpNotifier {
|
||||
notifier := new(HttpNotifier)
|
||||
notifier.URL = os.Getenv("APP_HTTP_NOTIFIER_URL")
|
||||
if notifier.URL == "" {
|
||||
log.Println("Unable to create HTTP notifier. APP_HTTP_NOTIFIER_URL not set.")
|
||||
return nil
|
||||
}
|
||||
notifier.From = os.Getenv("APP_NOTIFIER_FROM")
|
||||
if notifier.From == "" {
|
||||
notifier.From = "webhookd <noreply@nunux.org>"
|
||||
}
|
||||
notifier.To = os.Getenv("APP_NOTIFIER_TO")
|
||||
if notifier.To == "" {
|
||||
notifier.To = "hostmaster@nunux.org"
|
||||
}
|
||||
return notifier
|
||||
}
|
||||
|
||||
func (n HttpNotifier) Notify(text string, subject string) {
|
||||
log.Println("HTTP notification: ", subject)
|
||||
data := make(url.Values)
|
||||
data.Set("from", n.From)
|
||||
data.Set("to", n.To)
|
||||
data.Set("subject", subject)
|
||||
data.Set("text", text)
|
||||
|
||||
// Submit form
|
||||
resp, err := http.PostForm(n.URL, data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
37
src/tools/compress.go
Normal file
37
src/tools/compress.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package tools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func CompressFile(filename string) (zipfile string, err error) {
|
||||
zipfile = fmt.Sprintf("%s.gz", filename)
|
||||
in, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err := os.Create(zipfile)
|
||||
if err != nil {
|
||||
log.Println("Unable to create zip file", err)
|
||||
return
|
||||
}
|
||||
|
||||
// buffer readers from file, writes to pipe
|
||||
bufin := bufio.NewReader(in)
|
||||
|
||||
// gzip wraps buffer writer and wr
|
||||
gw := gzip.NewWriter(out)
|
||||
defer gw.Close()
|
||||
|
||||
_, err = bufin.WriteTo(gw)
|
||||
if err != nil {
|
||||
log.Println("Unable to write into the zip file", err)
|
||||
return
|
||||
}
|
||||
log.Println("Zip file created: ", zipfile)
|
||||
return
|
||||
}
|
136
src/webhookd.go
Normal file
136
src/webhookd.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/ncarlier/webhookd/hook"
|
||||
"github.com/ncarlier/webhookd/notification"
|
||||
"github.com/ncarlier/webhookd/tools"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
laddr = flag.String("l", ":8080", "HTTP service address (e.g.address, ':8080')")
|
||||
workingdir = os.Getenv("APP_WORKING_DIR")
|
||||
scriptsdir = os.Getenv("APP_SCRIPTS_DIR")
|
||||
)
|
||||
|
||||
type HookContext struct {
|
||||
Hook string
|
||||
Action string
|
||||
args []string
|
||||
}
|
||||
|
||||
func Notify(subject string, text string, outfilename string) {
|
||||
var notifier, err = notification.NotifierFactory()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if notifier == nil {
|
||||
log.Println("Notification provider not found.")
|
||||
return
|
||||
}
|
||||
|
||||
var zipfile string
|
||||
if outfilename != "" {
|
||||
zipfile, err = tools.CompressFile(outfilename)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
zipfile = outfilename
|
||||
}
|
||||
}
|
||||
|
||||
notifier.Notify(subject, text, zipfile)
|
||||
}
|
||||
|
||||
func RunScript(w http.ResponseWriter, context *HookContext) {
|
||||
scriptname := path.Join(scriptsdir, context.Hook, fmt.Sprintf("%s.sh", context.Action))
|
||||
log.Println("Exec script: ", scriptname)
|
||||
|
||||
cmd := exec.Command(scriptname, context.args...)
|
||||
var ErrorHandler func(err error, out string)
|
||||
ErrorHandler = func(err error, out string) {
|
||||
subject := fmt.Sprintf("Webhook %s/%s FAILED.", context.Hook, context.Action)
|
||||
Notify(subject, err.Error(), out)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// open the out file for writing
|
||||
outfilename := path.Join(workingdir, fmt.Sprintf("%s-%s.txt", context.Hook, context.Action))
|
||||
outfile, err := os.Create(outfilename)
|
||||
if err != nil {
|
||||
ErrorHandler(err, "")
|
||||
return
|
||||
}
|
||||
|
||||
defer outfile.Close()
|
||||
cmd.Stdout = outfile
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
ErrorHandler(err, "")
|
||||
return
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
ErrorHandler(err, outfilename)
|
||||
return
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("Webhook %s/%s SUCCEEDED.", context.Hook, context.Action)
|
||||
Notify(subject, "See attached file for logs.", outfilename)
|
||||
fmt.Fprintf(w, subject)
|
||||
}
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
context := new(HookContext)
|
||||
context.Hook = params["hookname"]
|
||||
context.Action = params["action"]
|
||||
|
||||
log.Println("Hook name: ", context.Hook)
|
||||
|
||||
var record, err = hook.RecordFactory(context.Hook)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&record)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
context.args = []string{record.GetURL(), record.GetName()}
|
||||
|
||||
RunScript(w, context)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if workingdir == "" {
|
||||
workingdir = os.TempDir()
|
||||
}
|
||||
if scriptsdir == "" {
|
||||
scriptsdir = "scripts"
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
rtr := mux.NewRouter()
|
||||
rtr.HandleFunc("/{hookname:[a-z]+}/{action:[a-z]+}", Handler).Methods("POST")
|
||||
|
||||
http.Handle("/", rtr)
|
||||
|
||||
log.Println("webhookd server listening...")
|
||||
log.Fatal(http.ListenAndServe(*laddr, nil))
|
||||
}
|
Loading…
Reference in New Issue
Block a user