Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions docs/content/docs/guide/incoming_webhook.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,114 @@ spec:
type: webhook-url
```

### Glob Pattern Matching in Targets

The `targets` field supports both exact string matching and glob patterns, allowing you to match multiple branches with a single rule.

**Glob patterns:** Use shell-style patterns:

- `*` - matches any characters (e.g., `feature/*` matches `feature/login`, `feature/api`)
- `?` - matches exactly one character (e.g., `v?` matches `v1`, `v2`)
- `[abc]` - matches one character from set (e.g., `[A-Z]*` matches any uppercase letter)
- `[0-9]` - matches digits (e.g., `v[0-9]*.[0-9]*` matches `v1.2`, `v10.5`)
- `{a,b,c}` - matches alternatives (e.g., `{dev,staging}/*` matches `dev/test` or `staging/test`)

**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.

#### Examples

**Match feature branches with glob:**

```yaml
apiVersion: "pipelinesascode.tekton.dev/v1alpha1"
kind: Repository
metadata:
name: repo
namespace: ns
spec:
url: "https://github.com/owner/repo"
incoming:
- targets:
- "feature/*" # Matches any branch starting with "feature/"
secret:
name: feature-webhook-secret
type: webhook-url
```

**Multiple webhooks with first-match-wins:**

```yaml
apiVersion: "pipelinesascode.tekton.dev/v1alpha1"
kind: Repository
metadata:
name: repo
namespace: ns
spec:
url: "https://github.com/owner/repo"
incoming:
# Production - checked first (most specific)
- targets:
- main
- "v[0-9]*.[0-9]*.[0-9]*" # Semver tags like v1.2.3
secret:
name: prod-webhook-secret
params:
- prod_env
type: webhook-url

# Feature branches - checked second
- targets:
- "feature/*"
- "bugfix/*"
secret:
name: feature-webhook-secret
params:
- dev_env
type: webhook-url

# Catch-all - checked last
- targets:
- "*" # Matches any branch not caught above
secret:
name: default-webhook-secret
type: webhook-url
```

**Mix exact matches and glob patterns:**

```yaml
incoming:
- targets:
- main # Exact match
- staging # Exact match
- "release/v[0-9]*.[0-9]*.[0-9]*" # Semver releases
- "hotfix/[A-Z]*-[0-9]*" # JIRA tickets (e.g., JIRA-123, PROJ-456)
- "{dev,test,qa}/*" # Alternation pattern
secret:
name: repo-incoming-secret
type: webhook-url
```

**Glob Pattern Syntax:**

- `*` - matches any characters (zero or more)
- `?` - matches exactly one character
- `[abc]` - matches one character: a, b, or c
- `[a-z]` - matches one character in range a to z
- `[0-9]` - matches one digit
- `{a,b,c}` - matches any of the alternatives (alternation)

**Best Practices:**

- Place production/sensitive webhooks first in the list
- Use exact matches for known branches when possible (faster than glob patterns)
- Use character classes `[0-9]`, `[A-Z]` for more precise matching
- Glob patterns match the entire branch name (no partial matches unless you use `*` prefix/suffix)
- Test your patterns: branch `feature-login` matches `feature-*` but not `*feature*`
- [Test your glob patterns online](https://www.digitalocean.com/community/tools/glob) before deploying to ensure they match only intended branches

### Using Incoming Webhooks

A PipelineRun is then annotated to target the incoming event and the main branch:

```yaml
Expand Down
35 changes: 31 additions & 4 deletions pkg/matcher/repo_runinfo_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package matcher

import (
"context"
"fmt"
"strings"

"github.com/gobwas/glob"
apipac "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
Expand Down Expand Up @@ -46,14 +48,39 @@ func GetRepo(ctx context.Context, cs *params.Run, repoName string) (*apipac.Repo
}

// IncomingWebhookRule will match a rule to an incoming rule, currently a rule is a target branch.
// Supports both exact string matching and glob patterns.
// Uses first-match-wins strategy: returns the first webhook with a matching target.
func IncomingWebhookRule(branch string, incomingWebhooks []apipac.Incoming) *apipac.Incoming {
// TODO: one day we will match the hook.Type here when we get something else than the dumb one (ie: slack)
for _, hook := range incomingWebhooks {
for _, v := range hook.Targets {
if v == branch {
return &hook
for i := range incomingWebhooks {
hook := &incomingWebhooks[i]

// Check each target in this webhook
for _, target := range hook.Targets {
matched, err := matchTarget(branch, target)
if err != nil {
// Skip invalid glob patterns and continue to next target
continue
}

if matched {
// First match wins - return immediately
return hook
}
}
}
return nil
}

// matchTarget checks if a branch matches a target pattern using glob matching.
// Supports both exact string matching and glob patterns (* for any chars, ? for single char).
// Glob patterns are more intuitive than regex and don't have ambiguity with special chars
// like dots in version strings (e.g., "v1.0" matches literally, not as a regex wildcard).
func matchTarget(branch, target string) (bool, error) {
g, err := glob.Compile(target)
if err != nil {
return false, fmt.Errorf("invalid glob pattern %q: %w", target, err)
}

return g.Match(branch), nil
}
Loading