373 lines
8.3 KiB
Markdown
373 lines
8.3 KiB
Markdown
# Webhooks
|
|
|
|
Continuous integration on easy mode.
|
|
Webhooks allow for a ton of functionality,
|
|
but we are going to use it to kick off a kubernetes job.
|
|
Effectivitely automating reloading content on a static website.
|
|
|
|
## Background
|
|
|
|
This docs website is a static site that is hosted inside an nginx container.
|
|
The storage for these redundant pods is a longhorn rwx pvc that gets stood up.
|
|
To initialize the storage a kubernetes job is run. This job does the following:
|
|
|
|
- git clones the `rskntroot/rskio` repo containing the artifacts required to render the site
|
|
- executes the `mkdocs` command to render the static site
|
|
|
|
So what if when we push to github, we setup a webhook that tells kubernetes to kick off that job?
|
|
Well, we achieve some form of automation.
|
|
So how do we do this?
|
|
|
|
## Setup
|
|
|
|
### RBAC
|
|
|
|
=== "ServiceAccount"
|
|
|
|
``` yaml
|
|
apiVersion: v1
|
|
kind: ServiceAccount
|
|
metadata:
|
|
name: webhook-job-trigger
|
|
```
|
|
|
|
=== "Dev Roles"
|
|
|
|
``` yaml
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: Role
|
|
metadata:
|
|
name: job-creator
|
|
namespace: dev
|
|
rules:
|
|
- apiGroups: ["batch"]
|
|
resources: ["jobs"]
|
|
verbs: ["create"]
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: RoleBinding
|
|
metadata:
|
|
name: job-creator-binding
|
|
namespace: dev
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: webhook-job-trigger
|
|
namespace: default
|
|
roleRef:
|
|
kind: Role
|
|
name: job-creator
|
|
apiGroup: rbac.authorization.k8s.io
|
|
```
|
|
|
|
=== "Prod Roles"
|
|
|
|
``` yaml
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: Role
|
|
metadata:
|
|
name: job-creator
|
|
namespace: prod
|
|
rules:
|
|
- apiGroups: ["batch"]
|
|
resources: ["jobs"]
|
|
verbs: ["create"]
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: RoleBinding
|
|
metadata:
|
|
name: job-creator-binding
|
|
namespace: prod
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: webhook-job-trigger
|
|
namespace: default
|
|
roleRef:
|
|
kind: Role
|
|
name: job-creator
|
|
apiGroup: rbac.authorization.k8s.io
|
|
```
|
|
|
|
### ConfigMap
|
|
|
|
We will create a config map from a directory including the following files.
|
|
|
|
#### ConvertJob
|
|
|
|
We are going to be using curl to call the kubernetes API directly,
|
|
so we need to convert our job from yaml to json.
|
|
|
|
Convert the job to JSON and save to `etc/mkdocs-dev.json`
|
|
|
|
=== "Job"
|
|
|
|
``` yaml
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
generateName: mkdocs-builder-
|
|
namespace: dev
|
|
spec:
|
|
ttlSecondsAfterFinished: 600
|
|
template:
|
|
spec:
|
|
containers:
|
|
- name: mkdocs
|
|
image: squidfunk/mkdocs-material
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
git clone --single-branch -b dev https://github.com/rskntroot/rskio.git --depth 1 /docs
|
|
cd /docs/mkdocs
|
|
mkdocs build --site-dir /output
|
|
volumeMounts:
|
|
- name: mkdocs-storage
|
|
mountPath: /output
|
|
restartPolicy: Never
|
|
volumes:
|
|
- name: mkdocs-storage
|
|
persistentVolumeClaim:
|
|
claimName: mkdocs-pvc
|
|
```
|
|
|
|
=== "Convert"
|
|
|
|
``` bash
|
|
mkdir etc
|
|
cat job.yml | yq -e -j | jq > etc/mkdocs-dev.json
|
|
```
|
|
|
|
The following docs we will assume that you also created `etc/mkdocs-main.json`.
|
|
|
|
#### Hooks
|
|
|
|
create `etc/hooks.yaml`
|
|
|
|
=== "etc/hooks.yaml"
|
|
|
|
``` yaml
|
|
- id: rskio-mkdocs
|
|
execute-command: /etc/webhook/reload.sh
|
|
command-working-directory: /etc/webhook
|
|
response-message: payload received
|
|
response-headers:
|
|
- name: Access-Control-Allow-Origin
|
|
value: "*"
|
|
pass-arguments-to-command:
|
|
- source: payload
|
|
name: ref
|
|
- source: payload
|
|
name: repository.full_name
|
|
trigger-rule:
|
|
and:
|
|
- match:
|
|
type: value
|
|
value: push
|
|
parameter:
|
|
source: header
|
|
name: X-GitHub-Event
|
|
- match:
|
|
type: value
|
|
value: rskntroot/rskio
|
|
parameter:
|
|
source: payload
|
|
name: repository.full_name
|
|
```
|
|
|
|
=== "Secret"
|
|
|
|
after testing come back to implement secrets
|
|
|
|
``` yaml
|
|
trigger-rule:
|
|
and:
|
|
- match:
|
|
type: payload-hmac-sha1
|
|
secret: mysecret
|
|
parameter:
|
|
source: header
|
|
name: X-Hub-Signature
|
|
```
|
|
|
|
apply the `configmap` and rollout restart the webhook deployment
|
|
|
|
#### Command
|
|
|
|
``` bash title="etc/reload.sh"
|
|
#!/bin/sh
|
|
|
|
REF=$1
|
|
REPO=$2
|
|
|
|
dispatch() {
|
|
NS=$1
|
|
JOB_JSON=$2
|
|
SA_PATH="/var/run/secrets/kubernetes.io/serviceaccount"
|
|
curl https://kubernetes.default.svc/apis/batch/v1/namespaces/${NS}/jobs \
|
|
-X POST \
|
|
-H "Authorization: Bearer $(cat ${SA_PATH}/token)" \
|
|
-H "Content-Type: application/json" \
|
|
--cacert "${SA_PATH}/ca.crt" \
|
|
-d "@${JOB_JSON}"
|
|
}
|
|
|
|
docs(){
|
|
case ${REF} in
|
|
refs/heads/dev)
|
|
dispatch dev "/etc/webhook/mkdocs-dev.json"
|
|
;;
|
|
refs/heads/main)
|
|
dispatch prod "/etc/webhook/mkdocs-main.json"
|
|
;;
|
|
*)
|
|
echo "skipping push to unsupported ref ${REF}"
|
|
exit 0
|
|
;;
|
|
esac
|
|
}
|
|
|
|
case ${REPO} in
|
|
rskntroot/rskio)
|
|
docs
|
|
;;
|
|
*)
|
|
echo "skipping push to unsupported repo ${REPO}"
|
|
;;
|
|
esac
|
|
```
|
|
|
|
#### Create
|
|
|
|
once all resources in `etc` are created run the following command:
|
|
|
|
``` bash
|
|
kubectl create configmap webhook-etc --from-file=etc
|
|
```
|
|
|
|
if you need to update anything run the following:
|
|
|
|
``` bash
|
|
kubectl delete configmap webhook-etc
|
|
kubectl create configmap webhook-etc --from-file=etc
|
|
```
|
|
|
|
### Resources
|
|
|
|
The following resources will complete the work
|
|
|
|
=== "Deployment"
|
|
|
|
``` yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: webhook-docs
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: webhook-docs
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: webhook-docs
|
|
spec:
|
|
serviceAccountName: webhook-job-trigger
|
|
containers:
|
|
- name: webhook-docs
|
|
image: ghcr.io/linuxserver-labs/webhook:latest
|
|
command: ["/app/webhook"]
|
|
args:
|
|
- -hooks=/etc/webhook/hooks.yaml
|
|
- -hotreload
|
|
- -verbose
|
|
volumeMounts:
|
|
- name: webhook-etc
|
|
mountPath: /etc/webhook
|
|
volumes:
|
|
- name: webhook-etc
|
|
configMap:
|
|
name: webhook-etc
|
|
defaultMode: 493 # 0755
|
|
```
|
|
|
|
=== "Service"
|
|
|
|
``` yaml
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: webhook
|
|
spec:
|
|
selector:
|
|
app: webhook-docs
|
|
ports:
|
|
- protocol: TCP
|
|
port: 9000
|
|
targetPort: 9000
|
|
type: ClusterIP
|
|
```
|
|
|
|
=== "Certificate"
|
|
|
|
``` yaml
|
|
apiVersion: cert-manager.io/v1
|
|
kind: Certificate
|
|
metadata:
|
|
name: io-rsk-dev-hooks-tls
|
|
spec:
|
|
secretName: io-rsk-dev-hooks-tls
|
|
issuerRef:
|
|
name: dev-step-issuer
|
|
kind: ClusterIssuer
|
|
commonName: hooks.dev.rsk.io
|
|
dnsNames:
|
|
- hooks.dev.rsk.io
|
|
privateKey:
|
|
algorithm: RSA
|
|
encoding: PKCS1
|
|
size: 2048
|
|
usages:
|
|
- server auth
|
|
- client auth
|
|
duration: 2160h # 90 days
|
|
renewBefore: 360h # 15 days
|
|
secretTemplate:
|
|
annotations:
|
|
kubeseal-secret: "true"
|
|
labels:
|
|
domain: hooks-dev-rsk-io
|
|
```
|
|
|
|
=== "Ingress"
|
|
|
|
``` yaml
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: webhook
|
|
spec:
|
|
entryPoints:
|
|
- websecure
|
|
routes:
|
|
- match: Host(`hooks.dev.rsk.io`)
|
|
kind: Rule
|
|
services:
|
|
- name: webhook
|
|
port: 9000
|
|
middlewares:
|
|
- name: ratelimit
|
|
tls:
|
|
secretName: io-rsk-hooks-tls
|
|
```
|
|
|
|
## Testing
|
|
|
|
``` bash
|
|
curl -X POST https://hooks.dev.rsk.io/hooks/rskio-mkdocs \
|
|
-H 'X-Github-Event: push' \
|
|
-H 'Content-type: application-json' \
|
|
-d '{"ref": "refs/heads/dev","repository": {"full_name":"rskntroot/rskio"}}'
|
|
```
|
|
|
|
!!! note "Github needs access to a public domain for this to work."
|