// Copyright (C) 2019 Marius Schellenberger package main import ( "io/ioutil" "log" "net" "net/http" "reflect" "regexp" "time" //corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) var debug = false const ( prAction = "X-Interceptor-Pr-Action" pushRef = "X-Interceptor-Push-Ref" commentHeader = "X-Interceptor-Comment" commentAction = "X-Interceptor-Comment-Action" labelHeader = "X-Interceptor-Label" removeLabelsHeader = "X-Interceptor-Remove-Labels" githubEvent = "X-GitHub-Event" giteaEvent = "X-Gitea-Event" issueHeader = "Issue" commitHeader = "Commit" ) func init() { log.SetFlags(log.Flags() | log.Lshortfile) } type Handler struct { git *Git } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { event := r.Header.Get(githubEvent) switch event { case "pull_request": h.prHandler(w, r) case "push": h.pushHandler(w, r) case "issue_comment": h.commentHandler(w, r) default: h.dumpHandler(w, r) } status := int(reflect.Indirect(reflect.ValueOf(w)).FieldByName("status").Int()) log.Printf("%s %s %s event:%s status:%d", r.RemoteAddr, r.Method, r.URL, event, status) } func (h *Handler) prHandler(w http.ResponseWriter, r *http.Request) { var prb PullRequestBody body, err := readBody(r.Body, &prb) if err != nil { logError(w, err.Error()) return } if contains(r.Header[prAction], prb.Action) { if l := r.Header.Get(labelHeader); l != "" { label, _ := getLabel(l) var labels []string for _, l := range prb.PullRequest.Labels { labels = append(labels, l.Name) } if !contains(labels, label) { w.WriteHeader(http.StatusBadRequest) return } } h := w.Header() h.Set(issueHeader, itoa(prb.Number)) h.Set(commitHeader, prb.PullRequest.Head.SHA) w.Write(body) return } w.WriteHeader(http.StatusBadRequest) } func (h *Handler) pushHandler(w http.ResponseWriter, r *http.Request) { var pb PushBody body, err := readBody(r.Body, &pb) if err != nil { logError(w, err.Error()) return } push := r.Header.Get(pushRef) //if contains(r.Header[pushAction], pb.Ref) { if push != "" && push == pb.Ref { h := w.Header() h.Set(commitHeader, pb.After) w.Write(body) return } w.WriteHeader(http.StatusBadRequest) } func (h *Handler) commentHandler(w http.ResponseWriter, r *http.Request) { var cb CommentBody body, err := readBody(r.Body, &cb) if err != nil { logError(w, err.Error()) return } if !contains(r.Header[commentAction], cb.Action) { w.WriteHeader(http.StatusBadRequest) return } //if contains(r.Header[commentAction], cb.Comment.Body) { comment := r.Header.Get(commentHeader) if comment == "" { w.WriteHeader(http.StatusBadRequest) return } m, err := regexp.MatchString("(?m)^"+comment+"$", cb.Comment.Body) if err != nil { logError(w, err.Error()) return } if m { issue := itoa(cb.Issue.Number) api := detectAPI(r) if api == "" { logError(w, "unable to detect git provider API") return } if l := r.Header.Get(labelHeader); l != "" { label, color := getLabel(l) var labels []string for _, l := range cb.Issue.Labels { labels = append(labels, l.Name) } if !contains(labels, label) { err := h.git.setLabel(cb.Repository, api, issue, label, color, r.Header[removeLabelsHeader]) if err != nil { logError(w, err.Error()) return } } } pr, err := h.git.getPullRequest(cb.Repository, api, issue) if err != nil { logError(w, err.Error()) return } h := w.Header() h.Set(issueHeader, issue) h.Set(commitHeader, pr.Head.SHA) w.Write(body) return } w.WriteHeader(http.StatusBadRequest) } func (h *Handler) dumpHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() if debug { body, err := ioutil.ReadAll(r.Body) if err != nil { logError(w, err.Error()) return } log.Printf("debug headers: %#v", r.Header) log.Printf("debug: %s", string(body)) } w.WriteHeader(http.StatusBadRequest) } func main() { cfg, err := rest.InClusterConfig() if err != nil { log.Fatalf("error configuring kube client: %s\n", err) } cl, err := kubernetes.NewForConfig(cfg) if err != nil { log.Fatalf("error creating new kube client: %s\n", err) } ns, err := getNS() if err != nil { log.Fatalf("error getting current namespace %s\n", err) } h := &Handler{ &Git{ sec: cl.CoreV1().Secrets(ns), cl: &http.Client{Transport: &http.Transport{ DialContext: (&net.Dialer{ Timeout: 30 * time.Second, }).DialContext, }}, }, } http.Handle("/", h) s := &http.Server{ ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, Addr: ":8080", } log.Println("started webhook listener") log.Fatal(s.ListenAndServe()) }