Skip to content

Commit 870b15b

Browse files
zakiskchmouel
authored andcommitted
feat(github): add configurable GitOps command prefix
- Users can set a GitOps command prefix in the Repository CR to run /<prefix> test, /<prefix> retest, /<prefix> cancel. - Configure prefix in Repository CR's settings field. - Apply prefix-aware matching for GitHub issue/commit comments, including ok-to-test SHA validation and target selection. - Update repo schema/docs and opscomments helpers for prefixed regex matching and command extraction. - Add provider parse tests and e2e coverage for GitHub prefix flows to validate behavior end-to-end. - Add documentation explaining the feature thoroughly. https://issues.redhat.com/browse/SRVKP-7197 Signed-off-by: Zaki Shaikh <zashaikh@redhat.com>
1 parent 71d5588 commit 870b15b

File tree

22 files changed

+929
-446
lines changed

22 files changed

+929
-446
lines changed

config/300-repositories.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,11 @@ spec:
507507
- update
508508
type: string
509509
type: object
510+
gitops_command_prefix:
511+
description: |-
512+
GitOpsCommandPrefix configures the prefix for the GitOps command.
513+
This is used to identify the GitOps command in the PipelineRun.
514+
type: string
510515
pipelinerun_provenance:
511516
description: |-
512517
PipelineRunProvenance configures how PipelineRun definitions are fetched.

docs/content/docs/guides/gitops-commands/advanced.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,47 @@ This ensures the comment is correctly formatted when processed.
4646

4747
For a practical example, see the [pac-boussole](https://github.com/openshift-pipelines/pac-boussole) project, which uses the `on-comment` annotation to create a PipelineRun experience similar to [Prow](https://docs.prow.k8s.io/).
4848

49+
## GitOps Command Prefix
50+
51+
{{< support_matrix github_app="true" github_webhook="true" forgejo="false" gitlab="false" bitbucket_cloud="false" bitbucket_datacenter="false" >}}
52+
53+
You can configure a custom prefix for GitOps commands in the Repository CR. This allows you to use commands like `/pac test` instead of the standard `/test`. This is useful when both [prow](https://docs.prow.k8s.io/) CI and Pipelines-as-Code are configured on a Repository and making comments causes issues and confusion.
54+
55+
Please note that custom GitOps commands are excluded from this prefix settings.
56+
57+
To configure a custom GitOps command prefix, set the `gitops_command_prefix` field in your Repository CR's `settings` section:
58+
59+
```yaml
60+
apiVersion: "pipelinesascode.tekton.dev/v1alpha1"
61+
kind: Repository
62+
metadata:
63+
name: my-repository
64+
namespace: pipelines-as-code
65+
spec:
66+
url: "https://github.com/organization/repository"
67+
settings:
68+
gitops_command_prefix: "pac"
69+
```
70+
71+
Note: Set the prefix as a plain word (e.g. `pac`). The Forward slash (`/`) is added automatically by Pipelines-as-Code.
72+
73+
With this configuration, you can use the following prefixed commands:
74+
75+
- `/pac test` - Trigger all matching PipelineRuns
76+
- `/pac test <pipelinerun-name>` - Trigger a specific PipelineRun
77+
- `/pac retest` - Retest failed PipelineRuns
78+
- `/pac retest <pipelinerun-name>` - Retest specific PipelineRun
79+
- `/pac cancel` - Cancel all running PipelineRuns
80+
- `/pac cancel <pipelinerun-name>` - Cancel Specific PipelineRun
81+
- `/pac ok-to-test` - Approve CI for external contributors
82+
- `/pac ok-to-test SHA` - Approve CI for external contributors for a specific SHA
83+
84+
Example:
85+
86+
```text
87+
/pac test
88+
```
89+
4990
## Cancelling a PipelineRun
5091

5192
**What it does:** The `/cancel` command stops running PipelineRuns by commenting on the pull request.

pkg/apis/pipelinesascode/v1alpha1/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ type Settings struct {
150150
// +kubebuilder:validation:Enum=source;default_branch
151151
PipelineRunProvenance string `json:"pipelinerun_provenance,omitempty"`
152152

153+
// GitOpsCommandPrefix configures the prefix for the GitOps command.
154+
// This is used to identify the GitOps command in the PipelineRun.
155+
// +optional
156+
GitOpsCommandPrefix string `json:"gitops_command_prefix,omitempty"`
157+
153158
// Policy defines authorization policies for the repository, controlling who can
154159
// trigger PipelineRuns under different conditions.
155160
// +optional

pkg/opscomments/comments.go

Lines changed: 60 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,11 @@ import (
55
"regexp"
66
"strings"
77

8-
"go.uber.org/zap"
9-
10-
"github.com/openshift-pipelines/pipelines-as-code/pkg/acl"
118
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
129
"github.com/openshift-pipelines/pipelines-as-code/pkg/events"
1310
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
1411
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
15-
)
16-
17-
var (
18-
testAllRegex = regexp.MustCompile(`(?m)^/test\s*$`)
19-
retestAllRegex = regexp.MustCompile(`(?m)^/retest\s*$`)
20-
testSingleRegex = regexp.MustCompile(`(?m)^/test[ \t]+\S+`)
21-
retestSingleRegex = regexp.MustCompile(`(?m)^/retest[ \t]+\S+`)
22-
oktotestRegex = regexp.MustCompile(acl.OKToTestCommentRegexp)
23-
cancelAllRegex = regexp.MustCompile(`(?m)^(/cancel)\s*$`)
24-
cancelSingleRegex = regexp.MustCompile(`(?m)^(/cancel)[ \t]+\S+`)
12+
"go.uber.org/zap"
2513
)
2614

2715
type EventType string
@@ -42,56 +30,93 @@ var (
4230
OkToTestCommentEventType = EventType("ok-to-test-comment")
4331
)
4432

45-
const (
46-
testComment = "/test"
47-
retestComment = "/retest"
48-
cancelComment = "/cancel"
49-
)
33+
func RetestAllRegex(prefix string) *regexp.Regexp {
34+
return regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%sretest\s*$`, prefix))
35+
}
36+
37+
func RetestSingleRegex(prefix string) *regexp.Regexp {
38+
return regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%sretest[ \t]+\S+`, prefix))
39+
}
40+
41+
func TestAllRegex(prefix string) *regexp.Regexp {
42+
return regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%stest\s*$`, prefix))
43+
}
44+
45+
func TestSingleRegex(prefix string) *regexp.Regexp {
46+
return regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%stest[ \t]+\S+`, prefix))
47+
}
48+
49+
func OkToTestRegex(prefix string) *regexp.Regexp {
50+
return regexp.MustCompile(fmt.Sprintf(`(^|\n)\s*%sok-to-test(?:\s+([a-fA-F0-9]{7,40}))?\s*(\r\n|\r|\n|$)`, prefix))
51+
}
52+
53+
func CancelAllRegex(prefix string) *regexp.Regexp {
54+
return regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%scancel\s*$`, prefix))
55+
}
56+
57+
func CancelSingleRegex(prefix string) *regexp.Regexp {
58+
return regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%scancel[ \t]+\S+`, prefix))
59+
}
5060

51-
func CommentEventType(comment string) EventType {
61+
func CommentEventType(comment, prefix string) EventType {
5262
switch {
53-
case retestAllRegex.MatchString(comment):
63+
case RetestAllRegex(prefix).MatchString(comment):
5464
return RetestAllCommentEventType
55-
case retestSingleRegex.MatchString(comment):
65+
case RetestSingleRegex(prefix).MatchString(comment):
5666
return RetestSingleCommentEventType
57-
case testAllRegex.MatchString(comment):
67+
case TestAllRegex(prefix).MatchString(comment):
5868
return TestAllCommentEventType
59-
case testSingleRegex.MatchString(comment):
69+
case TestSingleRegex(prefix).MatchString(comment):
6070
return TestSingleCommentEventType
61-
case oktotestRegex.MatchString(comment):
71+
case OkToTestRegex(prefix).MatchString(comment):
6272
return OkToTestCommentEventType
63-
case cancelAllRegex.MatchString(comment):
73+
case CancelAllRegex(prefix).MatchString(comment):
6474
return CancelCommentAllEventType
65-
case cancelSingleRegex.MatchString(comment):
75+
case CancelSingleRegex(prefix).MatchString(comment):
6676
return CancelCommentSingleEventType
6777
default:
6878
return NoOpsCommentEventType
6979
}
7080
}
7181

7282
// SetEventTypeAndTargetPR function will set the event type and target test pipeline run in an event.
73-
func SetEventTypeAndTargetPR(event *info.Event, comment string) {
74-
commentType := CommentEventType(comment)
75-
if commentType == RetestSingleCommentEventType || commentType == TestSingleCommentEventType {
76-
event.TargetTestPipelineRun = GetPipelineRunFromTestComment(comment)
83+
func SetEventTypeAndTargetPR(event *info.Event, comment, prefix string) {
84+
commentType := CommentEventType(comment, prefix)
85+
if commentType == RetestSingleCommentEventType {
86+
typeOfComment := prefix + "retest"
87+
event.TargetTestPipelineRun = getNameFromComment(typeOfComment, comment)
88+
}
89+
if commentType == TestSingleCommentEventType {
90+
typeOfComment := prefix + "test"
91+
event.TargetTestPipelineRun = getNameFromComment(typeOfComment, comment)
7792
}
7893
if commentType == CancelCommentAllEventType || commentType == CancelCommentSingleEventType {
7994
event.CancelPipelineRuns = true
8095
}
8196
if commentType == CancelCommentSingleEventType {
82-
event.TargetCancelPipelineRun = GetPipelineRunFromCancelComment(comment)
97+
typeOfComment := prefix + "cancel"
98+
event.TargetCancelPipelineRun = getNameFromComment(typeOfComment, comment)
8399
}
84100
event.EventType = commentType.String()
85101
event.TriggerComment = comment
86102
}
87103

88-
func IsOkToTestComment(comment string) bool {
89-
return oktotestRegex.MatchString(comment)
104+
func IsOkToTestComment(comment, prefix string) bool {
105+
return OkToTestRegex(prefix).MatchString(comment)
106+
}
107+
108+
func IsTestRetestComment(comment, prefix string) bool {
109+
return TestSingleRegex(prefix).MatchString(comment) || TestAllRegex(prefix).MatchString(comment) ||
110+
RetestSingleRegex(prefix).MatchString(comment) || RetestAllRegex(prefix).MatchString(comment)
111+
}
112+
113+
func IsCancelComment(comment, prefix string) bool {
114+
return CancelAllRegex(prefix).MatchString(comment) || CancelSingleRegex(prefix).MatchString(comment)
90115
}
91116

92117
// GetSHAFromOkToTestComment extracts the optional SHA from an /ok-to-test comment.
93-
func GetSHAFromOkToTestComment(comment string) string {
94-
matches := oktotestRegex.FindStringSubmatch(comment)
118+
func GetSHAFromOkToTestComment(comment, prefix string) string {
119+
matches := OkToTestRegex(prefix).FindStringSubmatch(comment)
95120
if len(matches) > 2 {
96121
return strings.TrimSpace(matches[2])
97122
}
@@ -143,17 +168,6 @@ func AnyOpsKubeLabelInSelector() string {
143168
OnCommentEventType.String())
144169
}
145170

146-
func GetPipelineRunFromTestComment(comment string) string {
147-
if strings.Contains(comment, testComment) {
148-
return getNameFromComment(testComment, comment)
149-
}
150-
return getNameFromComment(retestComment, comment)
151-
}
152-
153-
func GetPipelineRunFromCancelComment(comment string) string {
154-
return getNameFromComment(cancelComment, comment)
155-
}
156-
157171
func getNameFromComment(typeOfComment, comment string) string {
158172
splitTest := strings.Split(strings.TrimSpace(comment), typeOfComment)
159173
if len(splitTest) < 2 {
@@ -171,48 +185,3 @@ func getNameFromComment(typeOfComment, comment string) string {
171185
// trim spaces
172186
return strings.TrimSpace(firstArg[1])
173187
}
174-
175-
func GetPipelineRunAndBranchNameFromTestComment(comment string) (string, string, error) {
176-
if strings.Contains(comment, testComment) {
177-
return getPipelineRunAndBranchNameFromComment(testComment, comment)
178-
}
179-
return getPipelineRunAndBranchNameFromComment(retestComment, comment)
180-
}
181-
182-
func GetPipelineRunAndBranchNameFromCancelComment(comment string) (string, string, error) {
183-
return getPipelineRunAndBranchNameFromComment(cancelComment, comment)
184-
}
185-
186-
// getPipelineRunAndBranchNameFromComment function will take GitOps comment and split the comment
187-
// by /test, /retest or /cancel to return branch name and pipelinerun name.
188-
func getPipelineRunAndBranchNameFromComment(typeOfComment, comment string) (string, string, error) {
189-
var prName, branchName string
190-
splitTest := strings.Split(comment, typeOfComment)
191-
192-
// after the split get the second part of the typeOfComment (/test, /retest or /cancel)
193-
// as second part can be branch name or pipelinerun name and branch name
194-
// ex: /test branch:nightly, /test prname branch:nightly
195-
if splitTest[1] != "" && strings.Contains(splitTest[1], ":") {
196-
branchData := strings.Split(splitTest[1], ":")
197-
198-
// make sure no other word is supported other than branch word
199-
if !strings.Contains(branchData[0], "branch") {
200-
return prName, branchName, fmt.Errorf("the GitOps comment%s does not contain a branch word", branchData[0])
201-
}
202-
branchName = strings.Split(strings.TrimSpace(branchData[1]), " ")[0]
203-
204-
// if data after the split contains prname then fetch that
205-
prData := strings.Split(strings.TrimSpace(branchData[0]), " ")
206-
if len(prData) > 1 {
207-
prName = strings.TrimSpace(prData[0])
208-
}
209-
} else {
210-
// get the second part of the typeOfComment (/test, /retest or /cancel)
211-
// as second part contains pipelinerun name
212-
// ex: /test prname
213-
getFirstLine := strings.Split(splitTest[1], "\n")
214-
// trim spaces
215-
prName = strings.TrimSpace(getFirstLine[0])
216-
}
217-
return prName, branchName, nil
218-
}

0 commit comments

Comments
 (0)