From a021fab5434aaf79da40c91ca03b76735fab398d Mon Sep 17 00:00:00 2001 From: ston1th Date: Tue, 10 Dec 2019 00:04:27 +0100 Subject: [PATCH] move git stuff out of main --- README.md | 3 +- git.go | 219 +++++++++++++++++++++++++++++++++++++++++++++++ helper.go | 4 +- main.go | 247 +++++------------------------------------------------- types.go | 7 +- 5 files changed, 250 insertions(+), 230 deletions(-) create mode 100644 git.go diff --git a/README.md b/README.md index 727f918..f6dcade 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Commit: ## Issue Comment Event ``` -X-Interceptor-Comment-Action: "/test-deploy" +X-Interceptor-Comment: "/test-deploy" +X-Interceptor-Comment-Action: []string{"created"} X-Interceptor-Label: "test-deploy:#aabbcc" X-Interceptor-Remove-Labels: []string{"test", "deploy"} ``` diff --git a/git.go b/git.go new file mode 100644 index 0000000..91e9976 --- /dev/null +++ b/git.go @@ -0,0 +1,219 @@ +// Copyright (C) 2019 Marius Schellenberger + +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" +) + +const ( + contentType = "Content-Type" + jsonType = "application/json" + + secretName = "git-auth" +) + +type Git struct { + sec typedcorev1.SecretInterface + cl *http.Client +} + +func (g *Git) getSecret(host string) (username, password string, err error) { + sec, err := g.sec.Get(secretName, metav1.GetOptions{}) + if err != nil { + return + } + s, ok := sec.Data[host] + if !ok { + err = fmt.Errorf("no key found for host %s in secret %s/%s", host, sec.Namespace, sec.Name) + return + } + a := strings.SplitN(string(s), ":", 2) + if len(a) < 2 { + err = fmt.Errorf("missing credentials for host %s in secret %s/%s", host, sec.Namespace, sec.Name) + return + } + username = a[0] + password = a[1] + return +} + +func (g *Git) send(req *http.Request) (*http.Response, error) { + user, pass, err := g.getSecret(req.URL.Host) + if err != nil { + return nil, err + } + req.SetBasicAuth(user, pass) + req.Header.Set(contentType, jsonType) + return g.cl.Do(req) +} + +func (g *Git) getPullRequest(repo Repository, api, id string) (pr PullRequest, err error) { + u, err := url.Parse(repo.CloneURL) + if err != nil { + return + } + u.Path = strings.Join([]string{api, "repos", + repo.Owner.Login, repo.Name, + "pulls", id}, "/") + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return + } + resp, err := g.send(req) + if err != nil { + return + } + if resp.StatusCode != http.StatusOK { + err = errors.New("error getting pull request") + return + } + _, err = readBody(resp.Body, &pr) + return +} + +func (g *Git) addLabel(repo Repository, api, label, color string) (labelID int64, err error) { + labelID = -1 + u, err := url.Parse(repo.CloneURL) + if err != nil { + err = fmt.Errorf("error parsing repo URL: %s", err) + return + } + repoLabels := strings.Join([]string{api, "repos", + repo.Owner.Login, repo.Name, + "labels"}, "/") + u.Path = repoLabels + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return + } + + resp, err := g.send(req) + if err != nil { + err = fmt.Errorf("error getting labels of %s: %s", u, err) + return + } + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("error getting labels of %s", u) + return + } + var labels []Label + _, err = readBody(resp.Body, &labels) + if err != nil { + return + } + for _, l := range labels { + if l.Name == label { + labelID = l.ID + break + } + } + if labelID >= 0 { + return + } + buf := new(bytes.Buffer) + l := &Label{Name: label, Color: color} + err = json.NewEncoder(buf).Encode(l) + if err != nil { + return + } + req, err = http.NewRequest("POST", u.String(), buf) + if err != nil { + return + } + + resp, err = g.send(req) + if err != nil { + err = fmt.Errorf("error creating label %s for %s: %s", label, u, err) + return + } + if resp.StatusCode != http.StatusCreated { + err = fmt.Errorf("error creating label %s for %s", label, u) + } + var createdLabel Label + _, err = readBody(resp.Body, &createdLabel) + labelID = createdLabel.ID + return +} + +func (g *Git) setLabel(repo Repository, api, id, label, color string, remove []string) (err error) { + labelID, err := g.addLabel(repo, api, label, color) + if err != nil { + return + } + u, err := url.Parse(repo.CloneURL) + if err != nil { + return + } + + u.Path = strings.Join([]string{api, "repos", + repo.Owner.Login, repo.Name, + "issues", id, "labels"}, "/") + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return + } + resp, err := g.send(req) + if err != nil { + return fmt.Errorf("error getting issue labels of %s: %s", u, err) + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("error getting issue labels of %s", u) + } + var labels []Label + _, err = readBody(resp.Body, &labels) + if err != nil { + return + } + plint := &PostLabelsInt{[]int64{labelID}} + plstring := &PostLabelsString{[]string{label}} + var rm, found bool + for _, l := range labels { + if contains(remove, l.Name) { + rm = true + } else { + if l.Name == label { + found = true + } + plint.Labels = append(plint.Labels, l.ID) + plstring.Labels = append(plstring.Labels, l.Name) + } + } + if !rm && found { + return + } + + buf := new(bytes.Buffer) + switch api { + case githubAPI: + err = json.NewEncoder(buf).Encode(plstring) + case giteaAPI: + err = json.NewEncoder(buf).Encode(plint) + } + if err != nil { + return + } + req, err = http.NewRequest("PUT", u.String(), buf) + if err != nil { + return + } + + resp, err = g.send(req) + if err != nil { + return fmt.Errorf("error setting issue label %s for %s: %s", label, u, err) + } + if resp.StatusCode != http.StatusOK { + b, _ := readBody(resp.Body, nil) + err = fmt.Errorf("error setting issue label %s for %s: %s", label, u, string(b)) + } + return +} diff --git a/helper.go b/helper.go index d7a7395..328d453 100644 --- a/helper.go +++ b/helper.go @@ -80,6 +80,8 @@ func readBody(r io.ReadCloser, i interface{}) (body []byte, err error) { if debug { log.Printf("debug: %s", string(body)) } - err = json.Unmarshal(body, i) + if i != nil { + err = json.Unmarshal(body, i) + } return } diff --git a/main.go b/main.go index d618ede..ae34ced 100644 --- a/main.go +++ b/main.go @@ -3,24 +3,16 @@ package main import ( - "bytes" - "encoding/json" - "errors" - "fmt" "io/ioutil" "log" "net" "net/http" - "net/url" "reflect" "regexp" - "strings" "time" - //corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" - typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" ) @@ -28,230 +20,25 @@ var debug = false const ( prAction = "X-Interceptor-Pr-Action" - pushAction = "X-Interceptor-Push-Action" + pushRef = "X-Interceptor-Push-Ref" + commentHeader = "X-Interceptor-Comment" commentAction = "X-Interceptor-Comment-Action" labelHeader = "X-Interceptor-Label" removeLabelsHeader = "X-Interceptor-Remove-Labels" - contentType = "Content-Type" - jsonType = "application/json" - githubEvent = "X-GitHub-Event" giteaEvent = "X-Gitea-Event" issueHeader = "Issue" commitHeader = "Commit" - - secretName = "git-auth" ) func init() { log.SetFlags(log.Flags() | log.Lshortfile) } -func (h *Handler) getSecret(host string) (username, password string, err error) { - sec, err := h.sec.Get(secretName, metav1.GetOptions{}) - if err != nil { - return - } - s, ok := sec.Data[host] - if !ok { - err = fmt.Errorf("no key found for host %s in secret %s/%s", host, sec.Namespace, sec.Name) - return - } - a := strings.SplitN(string(s), ":", 2) - if len(a) < 2 { - err = fmt.Errorf("missing credentials for host %s in secret %s/%s", host, sec.Namespace, sec.Name) - return - } - username = a[0] - password = a[1] - return -} - -func (h *Handler) getPullRequest(repo Repository, api, id string) (*PullRequest, error) { - u, err := url.Parse(repo.CloneURL) - if err != nil { - return nil, err - } - u.Path = strings.Join([]string{api, "repos", - repo.Owner.Login, repo.Name, - "pulls", id}, "/") - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return nil, err - } - req.Header.Set(contentType, jsonType) - - user, pass, err := h.getSecret(u.Host) - if err != nil { - return nil, err - } - req.SetBasicAuth(user, pass) - - resp, err := h.cl.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - return nil, errors.New("error getting pull request") - } - pr := new(PullRequest) - _, err = readBody(resp.Body, pr) - if err != nil { - return nil, err - } - return pr, nil -} - -func (h *Handler) addLabel(repo Repository, api, label, color string) (labelID int64, err error) { - labelID = -1 - u, err := url.Parse(repo.CloneURL) - if err != nil { - err = fmt.Errorf("error parsing repo URL: %s", err) - return - } - repoLabels := strings.Join([]string{api, "repos", - repo.Owner.Login, repo.Name, - "labels"}, "/") - u.Path = repoLabels - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return - } - user, pass, err := h.getSecret(u.Host) - if err != nil { - return - } - req.SetBasicAuth(user, pass) - req.Header.Set(contentType, jsonType) - - resp, err := h.cl.Do(req) - if err != nil { - err = fmt.Errorf("error getting labels of %s: %s", u, err) - return - } - if resp.StatusCode != http.StatusOK { - err = fmt.Errorf("error getting labels of %s", u) - return - } - var labels []Label - _, err = readBody(resp.Body, &labels) - if err != nil { - return - } - for _, l := range labels { - if l.Name == label { - labelID = l.ID - break - } - } - if labelID >= 0 { - return - } - buf := new(bytes.Buffer) - l := &Label{Name: label, Color: color} - err = json.NewEncoder(buf).Encode(l) - if err != nil { - return - } - req, err = http.NewRequest("POST", u.String(), buf) - if err != nil { - return - } - req.Header.Set(contentType, jsonType) - req.SetBasicAuth(user, pass) - - resp, err = h.cl.Do(req) - if err != nil { - err = fmt.Errorf("error creating label %s for %s: %s", label, u, err) - return - } - if resp.StatusCode != http.StatusCreated { - err = fmt.Errorf("error creating label %s for %s", label, u) - } - var createdLabel Label - _, err = readBody(resp.Body, &createdLabel) - labelID = createdLabel.ID - return -} - -func (h *Handler) setLabel(repo Repository, api, id, label, color string, remove []string) (err error) { - labelID, err := h.addLabel(repo, api, label, color) - if err != nil { - return - } - u, err := url.Parse(repo.CloneURL) - if err != nil { - return - } - - u.Path = strings.Join([]string{api, "repos", - repo.Owner.Login, repo.Name, - "issues", id, "labels"}, "/") - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return - } - user, pass, err := h.getSecret(u.Host) - if err != nil { - return - } - req.SetBasicAuth(user, pass) - req.Header.Set(contentType, jsonType) - resp, err := h.cl.Do(req) - if err != nil { - return fmt.Errorf("error getting issue labels of %s: %s", u, err) - } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("error getting issue labels of %s", u) - } - var labels []Label - _, err = readBody(resp.Body, &labels) - if err != nil { - return - } - pl := &PostLabels{[]int64{labelID}} - var rm, found bool - for _, l := range labels { - if contains(remove, l.Name) { - rm = true - } else { - if l.Name == label { - found = true - } - pl.Labels = append(pl.Labels, l.ID) - } - } - if !rm && found { - return - } - - buf := new(bytes.Buffer) - err = json.NewEncoder(buf).Encode(pl) - if err != nil { - return - } - req, err = http.NewRequest("PUT", u.String(), buf) - if err != nil { - return - } - req.SetBasicAuth(user, pass) - req.Header.Set(contentType, jsonType) - - resp, err = h.cl.Do(req) - if err != nil { - return fmt.Errorf("error setting issue label %s for %s: %s", label, u, err) - } - if resp.StatusCode != http.StatusOK { - err = fmt.Errorf("error setting issue label %s for %s", label, u) - } - return -} - type Handler struct { - sec typedcorev1.SecretInterface - cl *http.Client + git *Git } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -307,7 +94,7 @@ func (h *Handler) pushHandler(w http.ResponseWriter, r *http.Request) { logError(w, err.Error()) return } - push := r.Header.Get(pushAction) + push := r.Header.Get(pushRef) //if contains(r.Header[pushAction], pb.Ref) { if push != "" && push == pb.Ref { h := w.Header() @@ -326,8 +113,12 @@ func (h *Handler) commentHandler(w http.ResponseWriter, r *http.Request) { 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(commentAction) + comment := r.Header.Get(commentHeader) if comment == "" { w.WriteHeader(http.StatusBadRequest) return @@ -351,7 +142,7 @@ func (h *Handler) commentHandler(w http.ResponseWriter, r *http.Request) { labels = append(labels, l.Name) } if !contains(labels, label) { - err := h.setLabel(cb.Repository, api, issue, label, color, r.Header[removeLabelsHeader]) + err := h.git.setLabel(cb.Repository, api, issue, label, color, r.Header[removeLabelsHeader]) if err != nil { logError(w, err.Error()) return @@ -359,7 +150,7 @@ func (h *Handler) commentHandler(w http.ResponseWriter, r *http.Request) { } } - pr, err := h.getPullRequest(cb.Repository, api, issue) + pr, err := h.git.getPullRequest(cb.Repository, api, issue) if err != nil { logError(w, err.Error()) return @@ -402,12 +193,14 @@ func main() { log.Fatalf("error getting current namespace %s\n", err) } h := &Handler{ - sec: cl.CoreV1().Secrets(ns), - cl: &http.Client{Transport: &http.Transport{ - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - }).DialContext, - }}, + &Git{ + sec: cl.CoreV1().Secrets(ns), + cl: &http.Client{Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + }).DialContext, + }}, + }, } http.Handle("/", h) diff --git a/types.go b/types.go index e444313..53cc128 100644 --- a/types.go +++ b/types.go @@ -23,7 +23,11 @@ type Label struct { Name string `json:"name"` } -type PostLabels struct { +type PostLabelsString struct { + Labels []string `json:"labels"` +} + +type PostLabelsInt struct { Labels []int64 `json:"labels"` } @@ -33,6 +37,7 @@ type PushBody struct { } type CommentBody struct { + Action string `json:"action"` Issue Issue `json:"issue"` Comment Comment `json:"comment"` Repository Repository `json:"repository"`