Handle updates via git webhooks

This commit is contained in:
simon987
2019-01-13 14:58:52 -05:00
parent a2b5de0e01
commit ef333b6b25
17 changed files with 514 additions and 70 deletions

158
api/git.go Normal file
View File

@@ -0,0 +1,158 @@
package api
import (
"crypto"
"crypto/hmac"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/Sirupsen/logrus"
"github.com/valyala/fasthttp"
"hash"
"src/task_tracker/config"
"src/task_tracker/storage"
"strings"
)
type GitPayload struct {
Ref string `json:"ref"`
Before string `json:"before"`
After string `json:"after"`
Repository struct {
Id int64 `json:"id"`
Owner struct {
Id int64 `json:"id"`
Username string `json:"username"`
Login string `json:"login"`
FullName string `json:"full_name"`
Email string `json:"email"`
} `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Private bool `json:"private"`
Fork bool `json:"fork"`
Size int64 `json:"size"`
HtmlUrl string `json:"html_url"`
SshUrl string `json:"ssh_url"`
CloneUrl string `json:"clone_url"`
DefaultBranch string `json:"default_branch"`
} `json:"repository"`
}
func (g GitPayload) String() string {
jsonBytes, _ := json.Marshal(g)
return string(jsonBytes)
}
func (api *WebAPI) ReceiveGitWebHook(r *Request) {
if !signatureValid(r) {
logrus.Error("WebHook signature does not match!")
r.Ctx.SetStatusCode(403)
_, _ = fmt.Fprintf(r.Ctx, "Signature does not match")
return
}
payload := &GitPayload{}
if r.GetJson(payload) {
logrus.WithFields(logrus.Fields{
"payload": payload,
}).Info("Received git WebHook")
}
if !isProductionBranch(payload) {
return
}
project := api.getAssociatedProject(payload)
if project == nil {
return
}
version := getVersion(payload)
project.Version = version
api.Database.UpdateProject(project)
}
func signatureValid(r *Request) (matches bool) {
signature := parseSignatureFromRequest(r.Ctx)
if signature == "" {
return false
}
body := r.Ctx.PostBody()
mac := hmac.New(getHashFuncFromConfig(), config.Cfg.WebHookSecret)
mac.Write(body)
expectedMac := hex.EncodeToString(mac.Sum(nil))
matches = strings.Compare(expectedMac, signature) == 0
logrus.WithFields(logrus.Fields{
"expected": expectedMac,
"signature": signature,
"matches": matches,
}).Trace("Validating WebHook signature")
return
}
func getHashFuncFromConfig() func() hash.Hash {
if config.Cfg.WebHookHash == "sha1" {
return crypto.SHA1.New
} else if config.Cfg.WebHookHash == "sha256" {
return crypto.SHA256.New
}
logrus.WithFields(logrus.Fields{
"hash": config.Cfg.WebHookHash,
}).Error("Invalid hash function from config")
return nil
}
func parseSignatureFromRequest(ctx *fasthttp.RequestCtx) string {
signature := string(ctx.Request.Header.Peek(config.Cfg.WebHookSigHeader))
sigParts := strings.Split(signature, "=")
signature = sigParts[len(sigParts)-1]
return signature
}
func (api *WebAPI) getAssociatedProject(payload *GitPayload) *storage.Project {
project := api.Database.GetProjectWithRepoName(payload.Repository.FullName)
logrus.WithFields(logrus.Fields{
"project": project,
}).Trace("Found project associated with WebHook")
return project
}
func isProductionBranch(payload *GitPayload) (isProd bool) {
isProd = strings.HasSuffix(payload.Ref, "master")
logrus.WithFields(logrus.Fields{
"isProd": isProd,
}).Trace("Identified if push event occured in production branch")
return
}
func getVersion(payload *GitPayload) (version string) {
version = payload.After
logrus.WithFields(logrus.Fields{
"version": version,
}).Trace("Got new version")
return
}

View File

@@ -58,6 +58,8 @@ func New() *WebAPI {
api.router.GET("/task/get/:project", LogRequest(api.TaskGetFromProject))
api.router.GET("/task/get", LogRequest(api.TaskGet))
api.router.POST("/git/receivehook", LogRequest(api.ReceiveGitWebHook))
return api
}

View File

@@ -8,7 +8,8 @@ import (
type CreateProjectRequest struct {
Name string `json:"name"`
GitUrl string `json:"git_url"`
CloneUrl string `json:"clone_url"`
GitRepo string `json:"git_repo"`
Version string `json:"version"`
Priority int64 `json:"priority"`
}
@@ -33,7 +34,8 @@ func (api *WebAPI) ProjectCreate(r *Request) {
project := &storage.Project{
Name: createReq.Name,
Version: createReq.Version,
GitUrl: createReq.GitUrl,
CloneUrl: createReq.CloneUrl,
GitRepo: createReq.GitRepo,
Priority: createReq.Priority,
}

View File

@@ -25,27 +25,34 @@ type GetWorkerResponse struct {
func (api *WebAPI) WorkerCreate(r *Request) {
workerReq := &CreateWorkerRequest{}
if r.GetJson(workerReq) {
identity := getIdentity(r)
if !r.GetJson(workerReq) {
return
}
if canCreateWorker(r, workerReq, identity) {
identity := getIdentity(r)
id, err := api.workerCreate(workerReq, getIdentity(r))
if err != nil {
handleErr(err, r)
} else {
r.OkJson(CreateWorkerResponse{
Ok: true,
WorkerId: id,
})
}
if !canCreateWorker(r, workerReq, identity) {
} else {
r.Json(CreateWorkerResponse{
Ok: false,
Message: "You are now allowed to create a worker",
}, 403)
}
logrus.WithFields(logrus.Fields{
"identity": identity,
"createWorkerRequest": workerReq,
}).Warn("Failed CreateWorkerRequest")
r.Json(CreateWorkerResponse{
Ok: false,
Message: "You are now allowed to create a worker",
}, 403)
return
}
id, err := api.workerCreate(workerReq, getIdentity(r))
if err != nil {
handleErr(err, r)
} else {
r.OkJson(CreateWorkerResponse{
Ok: true,
WorkerId: id,
})
}
}
@@ -99,6 +106,7 @@ func getIdentity(r *Request) *storage.Identity {
identity := storage.Identity{
RemoteAddr: r.Ctx.RemoteAddr().String(),
UserAgent: string(r.Ctx.UserAgent()),
}
return &identity