From 1f4cd72981acc528174208b674e6439f6b237321 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Wed, 12 Sep 2018 22:54:37 +0800 Subject: [PATCH] Add support for gitee.com webhook --- README.md | 1 + gitee_hook.go | 108 +++++++++++++++++++++++++++++++++++++++++++++ gitee_hook_test.go | 61 +++++++++++++++++++++++++ webhook.go | 2 + 4 files changed, 172 insertions(+) create mode 100644 gitee_hook.go create mode 100644 gitee_hook_test.go diff --git a/README.md b/README.md index 9f18501..24b1149 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Note that because the hook URL is used as an API endpoint, you shouldn't have an * [bitbucket](https://bitbucket.org) * [travis](https://travis-ci.org) * [gogs](https://gogs.io) +* [gitee](https://gitee.com) * generic ## Examples diff --git a/gitee_hook.go b/gitee_hook.go new file mode 100644 index 0000000..a3fde6e --- /dev/null +++ b/gitee_hook.go @@ -0,0 +1,108 @@ +package git + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "strings" +) + +// GiteeHook is webhook for gitee.com +type GiteeHook struct{} + +type giteePush struct { + Ref string `json:"ref"` +} + +// DoesHandle satisfies hookHandler. +func (g GiteeHook) DoesHandle(h http.Header) bool { + event := h.Get("X-Gitee-Event") + + // for Gitee you can use X-Gitee-Event header to test if you could handle the request + if event != "" { + return true + } + return false +} + +// Handle satisfies hookHandler. +func (g GiteeHook) Handle(w http.ResponseWriter, r *http.Request, repo *Repo) (int, error) { + if r.Method != "POST" { + return http.StatusMethodNotAllowed, errors.New("the request had an invalid method") + } + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return http.StatusRequestTimeout, errors.New("could not read body from request") + } + + err = g.handleToken(r, body, repo.Hook.Secret) + if err != nil { + return http.StatusBadRequest, err + } + + event := r.Header.Get("X-Gitee-Event") + if event == "" { + return http.StatusBadRequest, errors.New("the 'X-Gitee-Event' header is required but was missing") + } + + switch event { + case "Push Hook": + err = g.handlePush(body, repo) + if !hookIgnored(err) && err != nil { + return http.StatusBadRequest, err + } + + // return 400 if we do not handle the event type. + default: + return http.StatusBadRequest, nil + } + + return http.StatusOK, err +} + +// handleToken checks for an optional token in the request. Gitee's webhook tokens are just +// simple strings that get sent as a header with the hook request. If one +// exists, verify that it matches the secret in the Caddy configuration. +func (g GiteeHook) handleToken(r *http.Request, body []byte, secret string) error { + token := r.Header.Get("X-Gitee-Token") + if token != "" { + if secret == "" { + Logger().Print("Unable to verify request. Secret not set in caddyfile!\n") + } else { + if token != secret { + return errors.New("Unable to verify request. The token and specified secret do not match!") + } + } + } + + return nil +} + +func (g GiteeHook) handlePush(body []byte, repo *Repo) error { + var push giteePush + + err := json.Unmarshal(body, &push) + if err != nil { + return err + } + + // extract the branch being pushed from the ref string + // and if it matches with our locally tracked one, pull. + refSlice := strings.Split(push.Ref, "/") + if len(refSlice) != 3 { + return errors.New("the push request contained an invalid reference string") + } + + branch := refSlice[2] + if branch != repo.Branch { + return hookIgnoredError{hookType: hookName(g), err: fmt.Errorf("found different branch %v", branch)} + } + + Logger().Print("Received pull notification for the tracking branch, updating...\n") + repo.Pull() + + return nil +} diff --git a/gitee_hook_test.go b/gitee_hook_test.go new file mode 100644 index 0000000..1552932 --- /dev/null +++ b/gitee_hook_test.go @@ -0,0 +1,61 @@ +package git + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" +) + +func TestGiteeDeployPush(t *testing.T) { + repo := &Repo{Branch: "master", Hook: HookConfig{URL: "/gitee_deploy"}} + glHook := GiteeHook{} + + for i, test := range []struct { + body string + event string + responseBody string + code int + }{ + {"", "", "", 400}, + {"", "Push Hook", "", 400}, + {pushGiteeBodyOther, "Push Hook", "", 200}, + {pushGiteeBodyPartial, "Push Hook", "", 400}, + {"", "Some other Event", "", 400}, + } { + + req, err := http.NewRequest("POST", "/gitee_deploy", bytes.NewBuffer([]byte(test.body))) + if err != nil { + t.Fatalf("Test %v: Could not create HTTP request: %v", i, err) + } + + if test.event != "" { + req.Header.Add("X-Gitee-Event", test.event) + } + + rec := httptest.NewRecorder() + + code, err := glHook.Handle(rec, req, repo) + + if code != test.code { + t.Errorf("Test %d: Expected response code to be %d but was %d", i, test.code, code) + } + + if rec.Body.String() != test.responseBody { + t.Errorf("Test %d: Expected response body to be '%v' but was '%v'", i, test.responseBody, rec.Body.String()) + } + } + +} + +var pushGiteeBodyPartial = ` +{ + "ref": "" +} +` + +var pushGiteeBodyOther = ` +{ + "ref": "refs/heads/some-other-branch" +} +` diff --git a/webhook.go b/webhook.go index cb177ca..5d204c6 100644 --- a/webhook.go +++ b/webhook.go @@ -66,6 +66,7 @@ var handlers = map[string]hookHandler{ "generic": GenericHook{}, "travis": TravisHook{}, "gogs": GogsHook{}, + "gitee": GiteeHook{}, } // defaultHandlers is the list of handlers to choose from @@ -76,6 +77,7 @@ var defaultHandlers = []string{ "bitbucket", "travis", "gogs", + "gitee", } // ServeHTTP implements the middlware.Handler interface.