first commit
This commit is contained in:
commit
ad18bb8ad3
9 changed files with 309 additions and 0 deletions
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM golang:alpine as builder
|
||||||
|
RUN adduser -D -g '' appuser
|
||||||
|
RUN mkdir /build
|
||||||
|
ADD . /build/
|
||||||
|
WORKDIR /build
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -gcflags 'all=-e' -ldflags "-s -w" -o main .
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=builder /etc/passwd /etc/passwd
|
||||||
|
COPY --from=builder /build/main /main
|
||||||
|
USER appuser
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["/main"]
|
||||||
24
LICENSE
Normal file
24
LICENSE
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (C) 2018 Marius Schellenberger
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* The names of the authors and/or contributors may not be used to
|
||||||
|
endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
35
README.md
Normal file
35
README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# webhook-interseptor
|
||||||
|
|
||||||
|
The Webhook Interseptor checks for specific conditions when a webhook is received.
|
||||||
|
|
||||||
|
This way we can trigger cleanups when the PR is closed.
|
||||||
|
|
||||||
|
# HTTP Headers
|
||||||
|
|
||||||
|
## Push Event
|
||||||
|
|
||||||
|
```
|
||||||
|
X-Interseptor-Ref: refs/heads/master
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pull-Request Event
|
||||||
|
|
||||||
|
```
|
||||||
|
X-Interseptor-Action: [create|delete]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Issue Comment Event
|
||||||
|
|
||||||
|
**TBD**
|
||||||
|
|
||||||
|
```
|
||||||
|
X-Interseptor-Comment: /test
|
||||||
|
```
|
||||||
|
|
||||||
|
# Build and run
|
||||||
|
|
||||||
|
```
|
||||||
|
export KO_DOCKER_REPO='gcr.io/my-gcloud-project-name'
|
||||||
|
|
||||||
|
ko apply -f config/
|
||||||
|
```
|
||||||
4
config/00-namespace.yaml
Normal file
4
config/00-namespace.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: webhook-interseptor
|
||||||
26
config/01-podsecuritypolicy.yaml
Normal file
26
config/01-podsecuritypolicy.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
apiVersion: policy/v1beta1
|
||||||
|
kind: PodSecurityPolicy
|
||||||
|
metadata:
|
||||||
|
name: webhook-interseptor
|
||||||
|
spec:
|
||||||
|
privileged: false
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
volumes:
|
||||||
|
- 'secret'
|
||||||
|
hostNetwork: false
|
||||||
|
hostIPC: false
|
||||||
|
hostPID: false
|
||||||
|
runAsUser:
|
||||||
|
rule: 'RunAsAny'
|
||||||
|
seLinux:
|
||||||
|
rule: 'RunAsAny'
|
||||||
|
supplementalGroups:
|
||||||
|
rule: 'MustRunAs'
|
||||||
|
ranges:
|
||||||
|
- min: 1
|
||||||
|
max: 65535
|
||||||
|
fsGroup:
|
||||||
|
rule: 'MustRunAs'
|
||||||
|
ranges:
|
||||||
|
- min: 1
|
||||||
|
max: 65535
|
||||||
28
config/02-sa.yaml
Normal file
28
config/02-sa.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: webhook-interseptor
|
||||||
|
namespace: webhook-interseptor
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: webhook-interseptor
|
||||||
|
namespace: webhook-interseptor
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: webhook-interseptor
|
||||||
|
namespace: webhook-interseptor
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: webhook-interseptor
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: webhook-interseptor
|
||||||
28
config/deployment.yaml
Normal file
28
config/deployment.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: webhook-interseptor
|
||||||
|
namespace: webhook-interseptor
|
||||||
|
labels:
|
||||||
|
app: webhook-interseptor
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: webhook-interseptor
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: webhook-interseptor
|
||||||
|
spec:
|
||||||
|
serviceAccountName: webhook-interseptor
|
||||||
|
containers:
|
||||||
|
- name: webhook-interseptor
|
||||||
|
image: git.giftfish.de/ston1th/webhook-interseptor
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
15
config/service.yaml
Normal file
15
config/service.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: webhook-interseptor
|
||||||
|
namespace: webhook-interseptor
|
||||||
|
labels:
|
||||||
|
app: webhook-interseptor
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
selector:
|
||||||
|
app: webhook-interseptor
|
||||||
137
main.go
Normal file
137
main.go
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version string
|
||||||
|
|
||||||
|
type PR struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func prHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var pr PR
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("debug headers: %#v", r.Header)
|
||||||
|
log.Printf("debug body: %s", string(body))
|
||||||
|
err = json.Unmarshal(body, &pr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch pr.Action {
|
||||||
|
case "opened", "reopened", "synchronize":
|
||||||
|
if r.Header.Get("X-Interseptor-Action") == "create" {
|
||||||
|
w.Write(body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "closed":
|
||||||
|
if r.Header.Get("X-Interseptor-Action") == "delete" {
|
||||||
|
w.Write(body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Push struct {
|
||||||
|
Ref string `json:"ref"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var p Push
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("debug headers: %#v", r.Header)
|
||||||
|
log.Printf("debug: %s", string(body))
|
||||||
|
err = json.Unmarshal(body, &p)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Header.Get("X-Interseptor-Ref") == p.Ref {
|
||||||
|
w.Write(body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentBody struct {
|
||||||
|
Comment Comment `json:"comment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func commentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var c CommentBody
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("debug headers: %#v", r.Header)
|
||||||
|
log.Printf("debug: %s", string(body))
|
||||||
|
err = json.Unmarshal(body, &c)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Header.Get("X-Interseptor-Comment") == c.Comment.Body {
|
||||||
|
w.Write(body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("debug headers: %#v", r.Header)
|
||||||
|
log.Printf("debug: %s", string(body))
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
event := r.Header.Get("X-GitHub-Event")
|
||||||
|
log.Printf("%s %s %s: %s", r.RemoteAddr, r.Method, r.URL, event)
|
||||||
|
switch event {
|
||||||
|
case "pull_request":
|
||||||
|
prHandler(w, r)
|
||||||
|
case "push":
|
||||||
|
pushHandler(w, r)
|
||||||
|
//case "issue_comment":
|
||||||
|
// commentHandler(w, r)
|
||||||
|
default:
|
||||||
|
dumpHandler(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s := &http.Server{
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
IdleTimeout: 120 * time.Second,
|
||||||
|
Addr: ":8080",
|
||||||
|
}
|
||||||
|
log.Fatal(s.ListenAndServe())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue