Skip to content

Commit dc7ba6f

Browse files
committed
feat: support glob in incoming webhook targets
Add glob pattern matching support for incoming webhook targets using shell-style wildcards for intuitive branch filtering. - Add matchTarget() helper to match glob patterns - Implement IncomingWebhookRule() with first-match-wins semantics - Support glob wildcards: * (any chars), ? (single char), [0-9] (ranges), {a,b} (alternation) - Add comprehensive unit and e2e tests - Update documentation with glob syntax, examples, and best practices - Update sample repository with glob pattern examples Jira: https://issues.redhat.com/browse/SRVKP-7364 Signed-off-by: Akshay Pant <[email protected]> Assisted-by: Claude-Sonnet-4.5 (via Cursor)
1 parent b14b004 commit dc7ba6f

File tree

5 files changed

+676
-31
lines changed

5 files changed

+676
-31
lines changed

docs/content/docs/guide/incoming_webhook.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,114 @@ spec:
5959
type: webhook-url
6060
```
6161
62+
### Glob Pattern Matching in Targets
63+
64+
The `targets` field supports both exact string matching and glob patterns, allowing you to match multiple branches with a single rule.
65+
66+
**Glob patterns:** Use shell-style patterns:
67+
68+
- `*` - matches any characters (e.g., `feature/*` matches `feature/login`, `feature/api`)
69+
- `?` - matches exactly one character (e.g., `v?` matches `v1`, `v2`)
70+
- `[abc]` - matches one character from set (e.g., `[A-Z]*` matches any uppercase letter)
71+
- `[0-9]` - matches digits (e.g., `v[0-9]*.[0-9]*` matches `v1.2`, `v10.5`)
72+
- `{a,b,c}` - matches alternatives (e.g., `{dev,staging}/*` matches `dev/test` or `staging/test`)
73+
74+
**First-match-wins:** If multiple incoming webhooks match the same branch, the first matching webhook in the YAML order is used. Place more specific webhooks before general catch-all webhooks.
75+
76+
#### Examples
77+
78+
**Match feature branches with glob:**
79+
80+
```yaml
81+
apiVersion: "pipelinesascode.tekton.dev/v1alpha1"
82+
kind: Repository
83+
metadata:
84+
name: repo
85+
namespace: ns
86+
spec:
87+
url: "https://github.com/owner/repo"
88+
incoming:
89+
- targets:
90+
- "feature/*" # Matches any branch starting with "feature/"
91+
secret:
92+
name: feature-webhook-secret
93+
type: webhook-url
94+
```
95+
96+
**Multiple webhooks with first-match-wins:**
97+
98+
```yaml
99+
apiVersion: "pipelinesascode.tekton.dev/v1alpha1"
100+
kind: Repository
101+
metadata:
102+
name: repo
103+
namespace: ns
104+
spec:
105+
url: "https://github.com/owner/repo"
106+
incoming:
107+
# Production - checked first (most specific)
108+
- targets:
109+
- main
110+
- "v[0-9]*.[0-9]*.[0-9]*" # Semver tags like v1.2.3
111+
secret:
112+
name: prod-webhook-secret
113+
params:
114+
- prod_env
115+
type: webhook-url
116+
117+
# Feature branches - checked second
118+
- targets:
119+
- "feature/*"
120+
- "bugfix/*"
121+
secret:
122+
name: feature-webhook-secret
123+
params:
124+
- dev_env
125+
type: webhook-url
126+
127+
# Catch-all - checked last
128+
- targets:
129+
- "*" # Matches any branch not caught above
130+
secret:
131+
name: default-webhook-secret
132+
type: webhook-url
133+
```
134+
135+
**Mix exact matches and glob patterns:**
136+
137+
```yaml
138+
incoming:
139+
- targets:
140+
- main # Exact match
141+
- staging # Exact match
142+
- "release/v[0-9]*.[0-9]*.[0-9]*" # Semver releases
143+
- "hotfix/[A-Z]*-[0-9]*" # JIRA tickets (e.g., JIRA-123, PROJ-456)
144+
- "{dev,test,qa}/*" # Alternation pattern
145+
secret:
146+
name: repo-incoming-secret
147+
type: webhook-url
148+
```
149+
150+
**Glob Pattern Syntax:**
151+
152+
- `*` - matches any characters (zero or more)
153+
- `?` - matches exactly one character
154+
- `[abc]` - matches one character: a, b, or c
155+
- `[a-z]` - matches one character in range a to z
156+
- `[0-9]` - matches one digit
157+
- `{a,b,c}` - matches any of the alternatives (alternation)
158+
159+
**Best Practices:**
160+
161+
- Place production/sensitive webhooks first in the list
162+
- Use exact matches for known branches when possible (faster than glob patterns)
163+
- Use character classes `[0-9]`, `[A-Z]` for more precise matching
164+
- Glob patterns match the entire branch name (no partial matches unless you use `*` prefix/suffix)
165+
- Test your patterns: branch `feature-login` matches `feature-*` but not `*feature*`
166+
- [Test your glob patterns online](https://www.digitalocean.com/community/tools/glob) before deploying to ensure they match only intended branches
167+
168+
### Using Incoming Webhooks
169+
62170
A PipelineRun is then annotated to target the incoming event and the main branch:
63171

64172
```yaml

pkg/matcher/repo_runinfo_matcher.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package matcher
22

33
import (
44
"context"
5+
"fmt"
56
"strings"
67

8+
"github.com/gobwas/glob"
79
apipac "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
810
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
911
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
@@ -46,14 +48,37 @@ func GetRepo(ctx context.Context, cs *params.Run, repoName string) (*apipac.Repo
4648
}
4749

4850
// IncomingWebhookRule will match a rule to an incoming rule, currently a rule is a target branch.
51+
// Supports both exact string matching and glob patterns.
52+
// Uses first-match-wins strategy: returns the first webhook with a matching target.
4953
func IncomingWebhookRule(branch string, incomingWebhooks []apipac.Incoming) *apipac.Incoming {
5054
// TODO: one day we will match the hook.Type here when we get something else than the dumb one (ie: slack)
51-
for _, hook := range incomingWebhooks {
52-
for _, v := range hook.Targets {
53-
if v == branch {
54-
return &hook
55+
for i := range incomingWebhooks {
56+
hook := &incomingWebhooks[i]
57+
58+
// Check each target in this webhook
59+
for _, target := range hook.Targets {
60+
matched, err := matchTarget(branch, target)
61+
if err != nil {
62+
// Skip invalid glob patterns and continue to next target
63+
continue
64+
}
65+
66+
if matched {
67+
// First match wins - return immediately
68+
return hook
5569
}
5670
}
5771
}
5872
return nil
5973
}
74+
75+
// matchTarget checks if a branch matches a target pattern using glob matching.
76+
// Supports both exact string matching and glob patterns.
77+
func matchTarget(branch, target string) (bool, error) {
78+
g, err := glob.Compile(target)
79+
if err != nil {
80+
return false, fmt.Errorf("invalid glob pattern %q: %w", target, err)
81+
}
82+
83+
return g.Match(branch), nil
84+
}

0 commit comments

Comments
 (0)