added labels
This commit is contained in:
parent
56cbb383ab
commit
bfb79c3f3c
5 changed files with 243 additions and 29 deletions
14
README.md
14
README.md
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
2
build.sh
2
build.sh
|
|
@ -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}')
|
||||
|
|
|
|||
27
helper.go
27
helper.go
|
|
@ -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
221
main.go
|
|
@ -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())
|
||||
|
|
|
|||
8
types.go
8
types.go
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue