Skip to content

Commit b9ffa36

Browse files
committed
feat: add prow like PR management
This add a Pac PipelineRun that enables managing PRs through GitHub comments, replacing Prow-like commands with Pipelines as Code. - `/assign user1 user2` → Assign users to a PR. - `/unassign user1 user2` → Remove assigned users from a PR. - `/label bug enhancement` → Add labels to a PR. - `/unlabel bug` → Remove specific labels from a PR. - `/lgtm` → Approve a PR with a comment. - `/help` → Posts a comment listing all supported commands in a markdown table. - Uses embedded Python in a Tekton Task to interact with the GitHub API. - Extracts GitHub token from `git_auth_secret` for authentication. - Parses `{{ trigger_comment }}` to determine the appropriate action. - Handles API errors with proper logging and graceful failures. - Supports both `POST` and `DELETE` requests for GitHub issue APIs. Signed-off-by: Chmouel Boudjnah <[email protected]>
1 parent 4a0d7a8 commit b9ffa36

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

.tekton/prow.yaml

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
apiVersion: tekton.dev/v1
3+
kind: PipelineRun
4+
metadata:
5+
name: prow-commands
6+
annotations:
7+
pipelinesascode.tekton.dev/on-comment: "^/(help|(assign|unassign|label|unlabel)[ ].*)"
8+
pipelinesascode.tekton.dev/max-keep-runs: "5"
9+
spec:
10+
pipelineSpec:
11+
tasks:
12+
- name: manage-pr
13+
displayName: Manage PR Assignments & Labels
14+
taskSpec:
15+
steps:
16+
- name: manage-pr
17+
image: registry.access.redhat.com/ubi9/ubi
18+
env:
19+
- name: GITHUB_TOKEN
20+
valueFrom:
21+
secretKeyRef:
22+
name: "{{ git_auth_secret }}"
23+
key: "git-provider-token"
24+
script: |
25+
#!/usr/bin/env python3
26+
import os
27+
import re
28+
import requests
29+
import sys
30+
31+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
32+
if not GITHUB_TOKEN:
33+
print("❌ GITHUB_TOKEN environment variable is missing", file=sys.stderr)
34+
sys.exit(1)
35+
36+
API_BASE = "https://api.github.com/repos/{{ repo_owner }}/{{ repo_name }}/issues/{{ pull_request_number }}"
37+
HEADERS = {
38+
"Authorization": f"Bearer {GITHUB_TOKEN}",
39+
"Accept": "application/vnd.github.v3+json",
40+
"X-GitHub-Api-Version": "2022-11-28"
41+
}
42+
43+
COMMENT = """{{ trigger_comment }}"""
44+
match = re.match(r"^/(assign|unassign|label|unlabel|help|lgtm)\s*(.*)", COMMENT)
45+
46+
if not match:
47+
print(f"⚠️ No valid command found in comment: {COMMENT}", file=sys.stderr)
48+
sys.exit(1)
49+
50+
command, values = match.groups()
51+
values = values.split()
52+
53+
def make_request(method, url, data=None):
54+
try:
55+
if method == "POST":
56+
response = requests.post(url, json=data, headers=HEADERS)
57+
elif method == "DELETE":
58+
response = requests.delete(url, json=data, headers=HEADERS)
59+
else:
60+
return None
61+
response.raise_for_status()
62+
return response
63+
except requests.exceptions.RequestException as e:
64+
print(f"❌ API request failed: {e}", file=sys.stderr)
65+
sys.exit(1)
66+
67+
if command == "assign":
68+
API_URL = f"{API_BASE}/assignees"
69+
data = {"assignees": values}
70+
response = make_request("POST", API_URL, data)
71+
elif command == "unassign":
72+
API_URL = f"{API_BASE}/assignees"
73+
data = {"assignees": values}
74+
response = make_request("DELETE", API_URL, data)
75+
elif command == "label":
76+
API_URL = f"{API_BASE}/labels"
77+
data = {"labels": values}
78+
response = make_request("POST", API_URL, data)
79+
elif command == "unlabel":
80+
API_URL = f"{API_BASE}/labels/{'/'.join(values)}"
81+
for label in values:
82+
response = make_request("DELETE", f"{API_BASE}/labels/{label}")
83+
elif command == "help":
84+
API_URL = f"{API_BASE}/comments"
85+
help_text = """### 🤖 Available Commands
86+
| Command | Description |
87+
|---------|-------------|
88+
| `/assign user1 user2` | Assigns users to the PR |
89+
| `/unassign user1 user2` | Removes assigned users |
90+
| `/label bug feature` | Adds labels to the PR |
91+
| `/unlabel bug feature` | Removes labels from the PR |
92+
| `/lgtm` | Approves the PR with a comment |
93+
| `/help` | Shows this help message |
94+
"""
95+
response = make_request("POST", API_URL, {"body": help_text})
96+
print(f"✅ Posted help message")
97+
sys.exit(0)
98+
elif command == "lgtm":
99+
# Submit a review with "LGTM :+1:"
100+
review_url = f"https://api.github.com/repos/{{ repo_owner }}/{{ repo_name }}/pulls/{{ pull_request_number }}/reviews"
101+
review_data = {
102+
"body": "LGTM :+1:",
103+
"event": "APPROVE"
104+
}
105+
response = make_request("POST", review_url, review_data)
106+
107+
if response and response.status_code in [200, 201, 204]:
108+
print(f"✅ Successfully processed {command}: {', '.join(values) if values else ''}")
109+
else:
110+
print(f"❌ Failed to process {command}: {response.status_code if response else 'N/A'} - {response.text if response else 'N/A'}", file=sys.stderr)
111+
sys.exit(1)

docs/content/docs/guide/gitops_commands.md

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ Using the [on-comment]({{< relref "/docs/guide/matchingevents.md#matching-a-pipe
100100

101101
See the [on-comment]({{< relref "/docs/guide/matchingevents.md#matching-a-pipelinerun-on-a-regexp-in-a-comment" >}}) guide for more detailed information.
102102

103+
For a complete example, you can see how Pipelines-as-Code's own repo implemented some prow comments via the `on-comment` annotation:
104+
105+
<https://github.com/openshift-pipelines/pipelines-as-code/blob/main/.tekton/prow.yaml>
106+
103107
## Cancelling a PipelineRun
104108

105109
You can cancel a running PipelineRun by commenting on the Pull Request.

0 commit comments

Comments
 (0)