From b064dc4b4cfd9682a6e52e4751a301d7810b9280 Mon Sep 17 00:00:00 2001 From: Jean-Yves NOLEN Date: Fri, 15 May 2026 18:41:15 +0200 Subject: [PATCH] feat: add wildcard suport for repository --- pkg/matcher/repo_runinfo_matcher.go | 24 +++++- pkg/matcher/repo_runinfo_matcher_test.go | 97 ++++++++++++++++++++++++ pkg/webhook/validation_test.go | 63 +++++++++++++++ 3 files changed, 182 insertions(+), 2 deletions(-) diff --git a/pkg/matcher/repo_runinfo_matcher.go b/pkg/matcher/repo_runinfo_matcher.go index 8bbbad9051..f7205a9570 100644 --- a/pkg/matcher/repo_runinfo_matcher.go +++ b/pkg/matcher/repo_runinfo_matcher.go @@ -25,11 +25,14 @@ func MatchEventURLRepo(ctx context.Context, cs *params.Run, event *info.Event, n } for _, repo := range repositories.Items { repo.Spec.URL = strings.TrimSuffix(repo.Spec.URL, "/") - if repo.Spec.URL == event.URL { + match, err := matchRepo(event.URL, repo.Spec.URL) + if err != nil { + return nil, err + } + if match { return &repo, nil } } - return nil, nil } @@ -89,3 +92,20 @@ func matchTarget(branch, target string) (bool, error) { return g.Match(branch), nil } + +// matchTarget checks if a branch matches a target pattern using glob matching. +// Supports both exact string matching and glob patterns. +func matchRepo(repo, target string) (bool, error) { + if target == repo { + return true, nil + } + // Check unix glob match + globPattern, err := glob.Compile(target) + if err != nil { + return false, err + } + if globPattern.Match(repo) { + return true, nil + } + return false, nil +} diff --git a/pkg/matcher/repo_runinfo_matcher_test.go b/pkg/matcher/repo_runinfo_matcher_test.go index b74fd7f817..87cd57c2dc 100644 --- a/pkg/matcher/repo_runinfo_matcher_test.go +++ b/pkg/matcher/repo_runinfo_matcher_test.go @@ -468,6 +468,103 @@ func TestIncomingWebhookRule(t *testing.T) { } } +func TestMatchRepo(t *testing.T) { + tests := []struct { + name string + eventURL string + repoURL string + wantMatch bool + wantError bool + errorCheck string + }{ + // Exact matching + { + name: "exact match", + eventURL: "https://github.com/tektoncd/pipelines-as-code", + repoURL: "https://github.com/tektoncd/pipelines-as-code", + wantMatch: true, + }, + { + name: "no substring match", + eventURL: "https://github.com/forked/pipelines-as-code", + repoURL: "https://github.com/tektoncd/pipelines-as-code", + wantMatch: false, + }, + // Wildcard * - matches zero or more characters + { + name: "glob * - prefix pattern", + eventURL: "https://github.com/tektoncd/pipelines-as-code", + repoURL: "https://github.com/tektoncd/*", + wantMatch: true, + }, + { + name: "glob * - must match from start", + eventURL: "https://gitlab.com/tektoncd/pipelines-as-code", + repoURL: "https://github.com/tektoncd/*", + wantMatch: false, + }, + { + name: "glob * - substring match with wildcards", + eventURL: "https://github.com/tektoncd/pipelines-as-code", + repoURL: "*/tektoncd/*", + wantMatch: true, + }, + { + name: "glob * - catch-all", + eventURL: "https://github.com/tektoncd/pipelines-as-code", + repoURL: "*", + wantMatch: true, + }, + // Wildcard ? - matches exactly one character + { + name: "glob ? - single char match", + eventURL: "https://github.com/tektoncd/pipelines-as-code-v2", + repoURL: "https://github.com/tektoncd/pipelines-as-code-v?", + wantMatch: true, + }, + // Character classes [...] + { + name: "glob [range] - character class", + eventURL: "https://gitlab.com/tektoncd/pipelines-as-code", + repoURL: "https://[a-z]*.com/tektoncd/pipelines-as-code", + wantMatch: true, + }, + // Error handling + { + name: "invalid glob - unclosed bracket", + eventURL: "https://github.com/tektoncd/pipelines-as-code", + repoURL: "https://[github.com/tektoncd/pipelines-as-code", + wantMatch: false, + wantError: true, + errorCheck: "unexpected end of input", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotMatch, err := matchRepo(tt.eventURL, tt.repoURL) + + if tt.wantError { + if err == nil { + t.Errorf("matchRepo() expected error but got nil") + return + } + if tt.errorCheck != "" { + assert.ErrorContains(t, err, tt.errorCheck) + } + } else { + if err != nil { + t.Errorf("matchRepo() unexpected error = %v", err) + return + } + if gotMatch != tt.wantMatch { + t.Errorf("matchRepo() gotMatch = %v, want %v for eventURL=%q, repoURL=%q", gotMatch, tt.wantMatch, tt.eventURL, tt.repoURL) + } + } + }) + } +} + func TestMatchTarget(t *testing.T) { tests := []struct { name string diff --git a/pkg/webhook/validation_test.go b/pkg/webhook/validation_test.go index 03d31df279..ac7035c96c 100644 --- a/pkg/webhook/validation_test.go +++ b/pkg/webhook/validation_test.go @@ -220,3 +220,66 @@ func TestReconcilerAdmit(t *testing.T) { }) } } + +func TestMatchRepo(t *testing.T) { + tests := []struct { + name string + url string + wantError bool + errorCheck string + }{ + // Exact matching + { + name: "std https", + url: "https://github.com/tektoncd/pipelines-as-code", + wantError: false, + }, + { + name: "std http", + url: "http://github.com/tektoncd/pipelines-as-code", + wantError: false, + }, + // Wildcard * - matches zero or more characters + { + name: "glob * - prefix pattern", + url: "https://github.com/tektoncd/*", + wantError: false, + }, + { + name: "glob * - domain", + url: "https://*/tektoncd/pipelines-as-code", + wantError: false, + }, + { + name: "std not github ", + url: "https://gitlab.com/tektoncd/pipelines-as-code", + wantError: false, + }, + { + name: "std not github deep path", + url: "https://gitlab.com/tektoncd/group/pipelines-as-code", + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateRepositoryURL(tt.url, "") + + if tt.wantError { + if err == nil { + t.Errorf("validateRepositoryURL() expected error but got nil") + return + } + if tt.errorCheck != "" { + assert.ErrorContains(t, err, tt.errorCheck) + } + } else { + if err != nil { + t.Errorf("validateRepositoryURL() unexpected error = %v", err) + return + } + } + }) + } +}