add dummy routes and start decoding webhook
This commit is contained in:
parent
773810957f
commit
2a1845f04c
2 changed files with 140 additions and 9 deletions
84
main.go
84
main.go
|
@ -6,19 +6,23 @@ import (
|
|||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"os/exec"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"hacklab.nilfm.cc/quartzgun/renderer"
|
||||
"hacklab.nilfm.cc/quartzgun/router"
|
||||
. "hacklab.nilfm.cc/quartzgun/util"
|
||||
|
||||
|
||||
"forge.lightcrystal.systems/lightcrystal/memnarch/webhook"
|
||||
)
|
||||
|
||||
func testPayload(next http.Handler) http.Handler {
|
||||
handler := func(w http.ResponseWriter, req *http.Request) {
|
||||
data := &map[string]interface{}{}
|
||||
func decode(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
data := make(map[string]interface{})
|
||||
|
||||
err := json.NewDecoder(req.Body).Decode(data)
|
||||
err := json.NewDecoder(req.Body).Decode(&data)
|
||||
|
||||
if err == nil {
|
||||
AddContextValue(req, "data", data)
|
||||
|
@ -27,18 +31,80 @@ func testPayload(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
next.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(handler)
|
||||
})
|
||||
}
|
||||
|
||||
func runJob(secret string, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// validate signature
|
||||
_, err := webhook.Verify([]byte(secret), req)
|
||||
if err != nil {
|
||||
w.WriteHeader(422)
|
||||
return
|
||||
}
|
||||
// get repo from data
|
||||
data := req.Context().Value("data").(map[string]interface{})
|
||||
if data != nil {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
repoUrl := data["repository"].(map[string]interface{})["clone_url"].(string)
|
||||
owner := data["owner"].(map[string]interface{})["login"].(string)
|
||||
repo := data["repository"].(map[string]interface{})["name"].(string)
|
||||
obj := data["head_commit"].(map[string]interface{})["id"].(string)
|
||||
|
||||
// create working dir
|
||||
workingDir := filepath.Join("working", owner, repo, obj)
|
||||
if (os.MkdirAll(workingDir, 0750) != nil) {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
// from this point on we can tell the client they succeeded
|
||||
|
||||
// so we run the rest in a goroutine...
|
||||
go func() {
|
||||
// cd and checkout repo
|
||||
cmd := exec.Command("git", "clone", repoUrl)
|
||||
cmd.Dir = workingDir
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
// clone error - log it and quit
|
||||
return
|
||||
}
|
||||
// read memnarch action file
|
||||
|
||||
|
||||
// decode and perform action
|
||||
}()
|
||||
|
||||
AddContextValue(req, "data", "job submitted")
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
func seeJobsForRepo(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func seeJobsForObject(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func run(args []string) error {
|
||||
secret := args[1]
|
||||
|
||||
rtr := &router.Router{
|
||||
Fallback: *template.Must(template.ParseFiles("templates/error.html")),
|
||||
}
|
||||
|
||||
rtr.Post("/api/test", testPayload(renderer.JSON("data")))
|
||||
|
||||
rtr.Post("/echo", decode(renderer.JSON("data")))
|
||||
rtr.Post(`/do/(?P<job>\S+)`, decode(runJob(secret, renderer.JSON("data"))))
|
||||
rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>\S+)`, seeJobsForRepo(renderer.JSON("data")))
|
||||
rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>[^/]+)/(?P<object>\S+)`, seeJobsForObject(renderer.JSON("data")))
|
||||
|
||||
http.ListenAndServe(":9999", rtr);
|
||||
return nil
|
||||
|
|
65
webhook/webhook.go
Normal file
65
webhook/webhook.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package webhook
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Hook struct {
|
||||
Signature string
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
const signaturePrefix = ""
|
||||
const signatureLength = len(signaturePrefix) + 64
|
||||
|
||||
func signBody(secret, body []byte) []byte {
|
||||
computed := hmac.New(sha256.New, secret)
|
||||
computed.Write(body)
|
||||
return []byte(computed.Sum(nil))
|
||||
}
|
||||
|
||||
func (h *Hook) SignedBy(secret []byte) bool {
|
||||
if len(h.Signature) != signatureLength || !strings.HasPrefix(h.Signature, signaturePrefix) {
|
||||
return false
|
||||
}
|
||||
|
||||
actual := make([]byte, 20)
|
||||
hex.Decode(actual, []byte(h.Signature[5:]))
|
||||
|
||||
return hmac.Equal(signBody(secret, h.Payload), actual)
|
||||
}
|
||||
|
||||
func (h *Hook) Extract(dst interface{}) error {
|
||||
return json.Unmarshal(h.Payload, dst)
|
||||
}
|
||||
|
||||
func HookFrom(req *http.Request) (hook *Hook, err error) {
|
||||
hook = new(Hook)
|
||||
if !strings.EqualFold(req.Method, "POST") {
|
||||
return nil, errors.New("Unknown method!")
|
||||
}
|
||||
|
||||
if hook.Signature = req.Header.Get("X-Signature-SHA256"); len(hook.Signature) == 0 {
|
||||
return nil, errors.New("No signature!")
|
||||
}
|
||||
|
||||
hook.Payload, err = ioutil.ReadAll(req.Body)
|
||||
return
|
||||
}
|
||||
|
||||
func Verify(secret []byte, req *http.Request) (hook *Hook, err error) {
|
||||
hook, err = HookFrom(req)
|
||||
|
||||
//Compare HMACs
|
||||
if err == nil && !hook.SignedBy(secret) {
|
||||
err = errors.New("Invalid signature")
|
||||
}
|
||||
return
|
||||
}
|
Loading…
Reference in a new issue