mirror of
https://github.com/simon987/task_tracker.git
synced 2025-12-10 21:48:52 +00:00
Handle updates via git webhooks
This commit is contained in:
158
api/git.go
Normal file
158
api/git.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user