Skip to content

Commit b633ced

Browse files
authored
feat: implement preview environment PR commenter in Go (#152)
1 parent 31574f8 commit b633ced

8 files changed

Lines changed: 775 additions & 262 deletions

File tree

.gitignore

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
config
2-
__pycache__
1+
**/.DS_Store
2+
**/*_test.go
3+
**/*.md
4+
bin/
5+
dist/
6+
tmp/
7+
vendor/
8+
.env
9+
.idea/

Dockerfile

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
FROM python:3.14.3-alpine@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510
1+
# ---- build stage ----
2+
FROM golang:1.25-alpine AS builder
23

3-
RUN pip install --upgrade pip
4+
WORKDIR /src
45

5-
COPY requirements.txt /app/requirements.txt
6-
WORKDIR /app
7-
RUN pip install -r requirements.txt
8-
COPY main.py /app/main.py
9-
COPY ./src /app/src
6+
COPY go.mod go.sum ./
7+
RUN go mod download
108

11-
CMD [ "python", "-u", "main.py" ]
9+
COPY . .
10+
11+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/pr-bot .
12+
13+
# ---- runtime stage ----
14+
FROM gcr.io/distroless/static-debian12:nonroot
15+
16+
WORKDIR /
17+
18+
COPY --from=build /out/pr-bot /pr-bot
19+
20+
# No port needed; it runs as a worker
21+
ENTRYPOINT ["/pr-bot"]

README.md

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,94 @@
1-
# pr-bot
1+
# Preview Environment PR Commenter (Go)
22

3-
## Requirements
3+
A small Kubernetes service that watches **Argo CD Applications** representing preview environments and posts a **GitHub Pull Request comment** with:
4+
5+
- the latest commit SHA deployed,
6+
- an Argo CD link,
7+
- preview links rendered as **QR codes** (minted via a protected QR signing service),
8+
- Grafana metrics and Loki logs links.
9+
10+
It’s designed for GitOps/preview workflows where each PR spins up a temporary environment and you want an automatic “here’s where it is” comment.
411

5-
- You need a k8s cluster.
6-
- You need to deploy the resources below before to the `glueops-core` cluster
7-
- You need a git provider api token (Ex. Github Personal Access Token)
8-
- You need a captain domain
9-
10-
```yaml
1112
---
12-
apiVersion: v1
13-
kind: Secret
14-
metadata:
15-
name: git-provider-api-token
16-
type: Opaque
17-
data:
18-
token: <base64 encoded github token>
13+
14+
## How it works
15+
16+
On a fixed interval, the service:
17+
18+
1. Lists Argo CD `Applications` (`argoproj.io/v1alpha1`) from Kubernetes using the dynamic client.
19+
2. Filters to apps that:
20+
- have at least one `ownerReference`,
21+
- have annotation `preview_environment: "true"`,
22+
- have annotation `head_sha` and the SHA appears in `status.sync.revisions`,
23+
- have `status.health.status` of `Healthy` or `Degraded`,
24+
- have at least one `status.summary.externalURLs` entry,
25+
- contain required GitHub metadata annotations: `repository_organization`, `repository_name`, `pull_request_number`.
26+
3. Builds links to:
27+
- Argo CD application page,
28+
- Grafana “workload metrics” dashboard,
29+
- Loki logs dashboard.
30+
4. For each external URL, calls a QR mint endpoint to produce a signed QR image URL.
31+
5. Posts a markdown table comment to the matching PR via the GitHub API.
32+
6. De-duplicates per process lifetime using `head_sha` (in-memory).
33+
1934
---
20-
apiVersion: v1
21-
kind: ConfigMap
22-
metadata:
23-
name: glueops-captain-domain
24-
data:
25-
captain_domain: <example: nonprod.tenant.glueopshosted.rocks>
35+
36+
## Inputs expected on the Argo CD Application
37+
38+
### Required annotations
39+
40+
| Annotation | Required | Meaning |
41+
|---|:---:|---|
42+
| `preview_environment` || Must be `"true"` to be considered |
43+
| `head_sha` || Commit SHA expected to be deployed |
44+
| `repository_organization` || GitHub org/owner |
45+
| `repository_name` || GitHub repo name |
46+
| `pull_request_number` || PR number to comment on |
47+
48+
### Required status fields (set by Argo CD)
49+
50+
- `status.sync.revisions` must contain `head_sha`
51+
- `status.health.status` must be `Healthy` or `Degraded`
52+
- `status.summary.externalURLs` must contain at least one URL
53+
54+
---
55+
56+
## Configuration
57+
58+
Configuration is via environment variables:
59+
60+
| Env var | Default | Required | Description |
61+
|---|---:|:---:|---|
62+
| `NAMESPACE` | `glueops-core` | | Namespace used to read ConfigMap/Secret and list Applications |
63+
| `GITHUB_APP_SECRET_NAME` | `tenant-repo-creds` | | K8s Secret with GitHub App credentials |
64+
| `CAPTAIN_DOMAIN_K8S_CONFIGMAP_NAME` | `glueops-captain-domain` | | K8s ConfigMap with `captain_domain` |
65+
| `WATCH_FOR_APPS_DELAY_SECONDS` | `10` | | Poll interval in seconds |
66+
| `QR_MINT_TOKEN` | || Bearer token for QR signing endpoint |
67+
| `QR_TTL_SECONDS` | `600` | | Signed QR TTL in seconds |
68+
| `KUBECONFIG` | `~/.kube/config` | | Used only when not running in-cluster |
69+
70+
Hard-coded defaults:
71+
- HTTP timeout: 15 seconds
72+
- GitHub API version header: `2022-11-28`
73+
2674
---
27-
```
2875

29-
## Running the app
76+
## Kubernetes resources required
3077

31-
- Ensure you have the following set in your ```.env``` file (at **root** foolder):
78+
### ConfigMap: captain domain
3279

33-
```bash
34-
export GITHUB_TOKEN=<some-value>
35-
```
80+
A ConfigMap containing the base domain used for service links:
3681

37-
For cloud specific setup (to be authenticated to the captain cluster), check [here](https://github.com/GlueOps/terraform-module-cloud-aws-kubernetes-cluster/wiki)
82+
- Name: `CAPTAIN_DOMAIN_K8S_CONFIGMAP_NAME` (default `glueops-captain-domain`)
83+
- Key: `captain_domain`
3884

39-
- Then run
85+
Example:
4086

41-
```python
42-
python main.py
43-
```
87+
```yaml
88+
apiVersion: v1
89+
kind: ConfigMap
90+
metadata:
91+
name: glueops-captain-domain
92+
namespace: glueops-core
93+
data:
94+
captain_domain: example.internal

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/glueops/pull-request-bot
2+
3+
go 1.25.7
4+
5+
require (
6+
github.com/golang-jwt/jwt/v5 v5.3.1
7+
github.com/joho/godotenv v1.5.1
8+
k8s.io/api v0.35.1
9+
k8s.io/apimachinery v0.35.1
10+
k8s.io/client-go v0.35.1
11+
)

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
2+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
3+
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM=
4+
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
5+
k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA=

0 commit comments

Comments
 (0)