added labels

This commit is contained in:
ston1th 2019-12-06 23:20:20 +01:00
commit bfb79c3f3c
5 changed files with 243 additions and 29 deletions

View file

@ -9,7 +9,7 @@ This way we can trigger cleanups when the PR is closed.
## Push Event
```
X-Interceptor-Push-Ref: []string{"refs/heads/master"}
X-Interceptor-Push-Ref: string: "refs/heads/master"
```
Response Headers:
@ -34,16 +34,16 @@ Commit: <Pull-Request Head Commit Hash>
## Issue Comment Event
```
X-Interceptor-Comment-Action: "/test"
X-Interceptor-Comment-Action: "/test-deploy"
X-Interceptor-Label: "test-deploy:#aabbcc"
X-Interceptor-Remove-Labels: []string{"test", "deploy"}
```
### Actions
TBD:
* `/label <name>` - add a label `<name>` to the pull request
* `/test` - run tests pipeline (requires `test` label)
* `/deploy` - deploy pull request in preview environment (requires `deploy` label)
* `/test` - add `test` label and run test pipeline
* `/deploy` - add `deploy` label and run deploy pipeline
* `/test-deploy` - add `test-deploy` label and run test-deploy pipeline
Response Headers:

View file

@ -4,5 +4,5 @@ source env.txt
kubectl -n webhook-interceptor delete deploy webhook-interceptor
ko apply -f config/
kubectl apply -f secret.yaml
sleep 5
sleep 10
kubectl -n webhook-interceptor logs -f $(kubectl -n webhook-interceptor get pod -ojsonpath='{.items[0].metadata.name}')

View file

@ -4,10 +4,12 @@ package main
import (
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
)
@ -16,6 +18,10 @@ const (
giteaAPI = "api/v1"
)
func itoa(i int64) string {
return strconv.FormatInt(i, 10)
}
func getNS() (string, error) {
if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
return ns, nil
@ -36,13 +42,25 @@ func logError(w http.ResponseWriter, msg string) {
func contains(a []string, s string) bool {
for _, v := range a {
if strings.Contains(s, v) {
if v == s {
return true
}
}
return false
}
func getLabel(l string) (label, color string) {
a := strings.SplitN(l, ":", 2)
switch len(a) {
case 1:
label = a[0]
case 2:
label = a[0]
color = a[1]
}
return
}
func detectAPI(r *http.Request) string {
if r.Header.Get(giteaEvent) != "" {
return giteaAPI
@ -53,14 +71,13 @@ func detectAPI(r *http.Request) string {
return ""
}
func readBody(r *http.Request, i interface{}) (body []byte, err error) {
defer r.Body.Close()
body, err = ioutil.ReadAll(r.Body)
func readBody(r io.ReadCloser, i interface{}) (body []byte, err error) {
defer r.Close()
body, err = ioutil.ReadAll(r)
if err != nil {
return
}
if debug {
log.Printf("debug headers: %#v", r.Header)
log.Printf("debug: %s", string(body))
}
err = json.Unmarshal(body, i)

221
main.go
View file

@ -3,7 +3,9 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
@ -11,7 +13,7 @@ import (
"net/http"
"net/url"
"reflect"
"strconv"
"regexp"
"strings"
"time"
@ -25,9 +27,11 @@ import (
var debug = false
const (
prAction = "X-Interceptor-Pr-Action"
pushAction = "X-Interceptor-Push-Action"
commentAction = "X-Interceptor-Comment-Action"
prAction = "X-Interceptor-Pr-Action"
pushAction = "X-Interceptor-Push-Action"
commentAction = "X-Interceptor-Comment-Action"
labelHeader = "X-Interceptor-Label"
removeLabelsHeader = "X-Interceptor-Remove-Labels"
contentType = "Content-Type"
jsonType = "application/json"
@ -89,15 +93,162 @@ func (h *Handler) getPullRequest(repo Repository, api, id string) (*PullRequest,
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New("error getting pull request")
}
pr := new(PullRequest)
err = json.NewDecoder(resp.Body).Decode(pr)
_, 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
@ -121,14 +272,27 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (h *Handler) prHandler(w http.ResponseWriter, r *http.Request) {
var prb PullRequestBody
body, err := readBody(r, &prb)
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, strconv.FormatInt(prb.Number, 10))
h.Set(issueHeader, itoa(prb.Number))
h.Set(commitHeader, prb.PullRequest.Head.SHA)
w.Write(body)
return
@ -138,12 +302,14 @@ func (h *Handler) prHandler(w http.ResponseWriter, r *http.Request) {
func (h *Handler) pushHandler(w http.ResponseWriter, r *http.Request) {
var pb PushBody
body, err := readBody(r, &pb)
body, err := readBody(r.Body, &pb)
if err != nil {
logError(w, err.Error())
return
}
if contains(r.Header[pushAction], pb.Ref) {
push := r.Header.Get(pushAction)
//if contains(r.Header[pushAction], pb.Ref) {
if push != "" && push == pb.Ref {
h := w.Header()
h.Set(commitHeader, pb.After)
w.Write(body)
@ -154,20 +320,45 @@ func (h *Handler) pushHandler(w http.ResponseWriter, r *http.Request) {
func (h *Handler) commentHandler(w http.ResponseWriter, r *http.Request) {
var cb CommentBody
body, err := readBody(r, &cb)
body, err := readBody(r.Body, &cb)
if err != nil {
logError(w, err.Error())
return
}
//if label := r.Header.Get(labelHeader); label != "" {
//}
if contains(r.Header[commentAction], cb.Comment.Body) {
issue := strconv.FormatInt(cb.Issue.Number, 10)
//if contains(r.Header[commentAction], cb.Comment.Body) {
comment := r.Header.Get(commentAction)
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.setLabel(cb.Repository, api, issue, label, color, r.Header[removeLabelsHeader])
if err != nil {
logError(w, err.Error())
return
}
}
}
pr, err := h.getPullRequest(cb.Repository, api, issue)
if err != nil {
logError(w, err.Error())

View file

@ -18,7 +18,13 @@ type Head struct {
}
type Label struct {
Name string `json:"name"`
ID int64 `json:"id"`
Color string `json:"color"`
Name string `json:"name"`
}
type PostLabels struct {
Labels []int64 `json:"labels"`
}
type PushBody struct {