Skip to content

Commit

Permalink
feat: support commas in PipelineRun annotations
Browse files Browse the repository at this point in the history
This commit introduces support for using commas within annotations by
allowing users to escape them with the HTML entity `,`. This ensures
compatibility with annotations that rely on comma-separated values while
also permitting the inclusion of literal commas in file paths or branch
names.

This enhancement maintains backward compatibility while enabling more
precise and flexible use of annotations.

**Jira**: https://issues.redhat.com/browse/SRVKP-6790
**Signed-off-by**: Chmouel Boudjnah <[email protected]>

Signed-off-by: Chmouel Boudjnah <[email protected]>
  • Loading branch information
chmouel committed Nov 27, 2024
1 parent fd6a243 commit 4878a24
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 12 deletions.
14 changes: 12 additions & 2 deletions docs/content/docs/guide/authoringprs.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,15 @@ provider events target the branch `main` and it's coming from a `[pull_request]`
Multiple target branch can be specified separated by comma, i.e:

```yaml
[main, release-nightly]
pipelinesascode.tekton.dev/on-target-branch: [main, release-nightly]
```

If you want to match a branch that has a comma in its name you can html escape
it with the `&#44;` entity, for example if you want to match main and the branch
called `release,nightly` you can do this:

```yaml
pipelinesascode.tekton.dev/on-target-branch: [main, release&#44;nightly]
```

You can match on `pull_request` events as above, and you can as well match
Expand Down Expand Up @@ -140,7 +148,9 @@ To trigger a `PipelineRun` based on specific path changes in an event, use the
annotation `pipelinesascode.tekton.dev/on-path-change`.

Multiple paths can be specified, separated by commas. The first glob matching
the files changes in the PR will trigger the `PipelineRun`.
the files changes in the PR will trigger the `PipelineRun`. If you want to match
a file or path that has a comma you can html escape it with the `&#44;` html
entity.

You still need to specify the event type and target branch. If you have a [CEL
expression](#matching-pipelinerun-by-path-change) the `on-path-change`
Expand Down
6 changes: 4 additions & 2 deletions pkg/matcher/annotation_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,15 @@ func getAnnotationValues(annotation string) ([]string, error) {

// if it's not an array then it would be a single string
if !strings.HasPrefix(annotation, "[") {
return []string{annotation}, nil
// replace &#44; with comma so users can have comma in the annotation
annot := strings.ReplaceAll(annotation, "&#44;", ",")
return []string{annot}, nil
}

// Split all tasks by comma and make sure to trim spaces in there
split := strings.Split(re.FindStringSubmatch(annotation)[1], ",")
for i := range split {
split[i] = strings.TrimSpace(split[i])
split[i] = strings.TrimSpace(strings.ReplaceAll(split[i], "&#44;", ","))
}

if split[0] == "" {
Expand Down
126 changes: 126 additions & 0 deletions pkg/matcher/annotation_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,116 @@ func TestMatchPipelinerunAnnotationAndRepositories(t *testing.T) {
},
},
},
{ //nolint:dupl
name: "match/on-path-change-ignore/with commas",
wantLog: "Skipping pipelinerun with name: pipeline-target-ns",
wantPRName: pipelineTargetNSName,
args: annotationTestArgs{
fileChanged: []struct {
FileName string
Status string
NewFile bool
RenamedFile bool
DeletedFile bool
}{
{
FileName: "doc/gen,foo,bar.md",
Status: "added",
NewFile: true,
RenamedFile: false,
DeletedFile: false,
},
},
pruns: []*tektonv1.PipelineRun{
{
ObjectMeta: metav1.ObjectMeta{
Name: pipelineTargetNSName,
Annotations: map[string]string{
keys.OnTargetBranch: mainBranch,
keys.OnEvent: "[pull_request]",
keys.OnPathChange: "[doc/gen&#44;*]",
},
},
},
},
runevent: info.Event{
URL: targetURL,
TriggerTarget: "pull_request",
EventType: "pull_request",
BaseBranch: mainBranch,
HeadBranch: "unittests",
PullRequestNumber: 1000,
Organization: "mylittle",
Repository: "pony",
},
data: testclient.Data{
Repositories: []*v1alpha1.Repository{
testnewrepo.NewRepo(
testnewrepo.RepoTestcreationOpts{
Name: "test-good",
URL: targetURL,
InstallNamespace: targetNamespace,
},
),
},
},
},
},
{ //nolint:dupl
name: "ignored/on-path-change-ignore/no path change",
wantLog: "Skipping pipelinerun with name: pipeline-target-ns",
wantErr: true,
args: annotationTestArgs{
fileChanged: []struct {
FileName string
Status string
NewFile bool
RenamedFile bool
DeletedFile bool
}{
{
FileName: "foo/generated/gen.md",
Status: "added",
NewFile: true,
RenamedFile: false,
DeletedFile: false,
},
},
pruns: []*tektonv1.PipelineRun{
{
ObjectMeta: metav1.ObjectMeta{
Name: pipelineTargetNSName,
Annotations: map[string]string{
keys.OnTargetBranch: mainBranch,
keys.OnEvent: "[pull_request]",
keys.OnPathChange: "[doc/***]",
},
},
},
},
runevent: info.Event{
URL: targetURL,
TriggerTarget: "pull_request",
EventType: "pull_request",
BaseBranch: mainBranch,
HeadBranch: "unittests",
PullRequestNumber: 1000,
Organization: "mylittle",
Repository: "pony",
},
data: testclient.Data{
Repositories: []*v1alpha1.Repository{
testnewrepo.NewRepo(
testnewrepo.RepoTestcreationOpts{
Name: "test-good",
URL: targetURL,
InstallNamespace: targetNamespace,
},
),
},
},
},
},
{
name: "ignored/on-path-change-ignore/include and ignore path",
wantLog: "Skipping pipelinerun with name: pipeline-target-ns",
Expand Down Expand Up @@ -1791,6 +1901,22 @@ func Test_getAnnotationValues(t *testing.T) {
want: []string{"foo"},
wantErr: false,
},
{
name: "get-annotation-string-html-encoded-comma-list",
args: args{
annotation: "[foo&#44;,bar]",
},
want: []string{"foo,", "bar"},
wantErr: false,
},
{
name: "get-annotation-string-html-encoded-comma",
args: args{
annotation: "foo&#44;bar",
},
want: []string{"foo,bar"},
wantErr: false,
},
{
name: "get-annotation-multiples",
args: args{
Expand Down
13 changes: 13 additions & 0 deletions test/gitea_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,19 @@ func TestGiteaOnPathChange(t *testing.T) {
defer f()
}

func TestGiteaBranchWithComma(t *testing.T) {
topts := &tgitea.TestOpts{
TargetEvent: triggertype.PullRequest.String(),
DefaultBranch: "branch,with,comma",
YAMLFiles: map[string]string{
".tekton/pr.yaml": "testdata/pipelinerun-target-branch-with-comma.yaml",
},
CheckForStatus: "success",
}
_, f := tgitea.TestPR(t, topts)
defer f()
}

// TestGiteaOnPathChangeIgnore will test that pipelinerun is not triggered when
// a path is ignored but all other will do.
func TestGiteaOnPathChangeIgnore(t *testing.T) {
Expand Down
9 changes: 5 additions & 4 deletions test/pkg/gitea/scm.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func GetIssueTimeline(ctx context.Context, topts *TestOpts) (Timelines, error) {
return tls, nil
}

func CreateGiteaRepo(giteaClient *gitea.Client, user, name, hookURL string, onOrg bool, logger *zap.SugaredLogger) (*gitea.Repository, error) {
func CreateGiteaRepo(giteaClient *gitea.Client, user, name, defaultBranch, hookURL string, onOrg bool, logger *zap.SugaredLogger) (*gitea.Repository, error) {
var repo *gitea.Repository
var err error
// Create a new repo
Expand All @@ -131,9 +131,10 @@ func CreateGiteaRepo(giteaClient *gitea.Client, user, name, hookURL string, onOr
} else {
logger.Infof("Creating gitea repository %s for user %s", name, user)
repo, _, err = giteaClient.AdminCreateRepo(user, gitea.CreateRepoOption{
Name: name,
Description: "This is a repo it's a wonderful thing",
AutoInit: true,
Name: name,
Description: "This is a repo it's a wonderful thing",
AutoInit: true,
DefaultBranch: defaultBranch,
})
}
if err != nil {
Expand Down
22 changes: 18 additions & 4 deletions test/pkg/gitea/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
)

type TestOpts struct {
TargetRepoName string
StatusOnlyLatest bool
OnOrg bool
NoPullRequestCreation bool
Expand Down Expand Up @@ -102,10 +103,20 @@ func TestPR(t *testing.T, topts *TestOpts) (context.Context, func()) {
if topts.TargetRefName == "" {
topts.TargetRefName = names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test")
topts.TargetNS = topts.TargetRefName
assert.NilError(t, pacrepo.CreateNS(ctx, topts.TargetNS, topts.ParamsRun))
}
if err := pacrepo.CreateNS(ctx, topts.TargetNS, topts.ParamsRun); err != nil {
t.Logf("error creating namespace %s: %v", topts.TargetNS, err)
}

if topts.TargetRepoName == "" {
topts.TargetRepoName = topts.TargetRefName
}

repoInfo, err := CreateGiteaRepo(topts.GiteaCNX.Client, topts.Opts.Organization, topts.TargetRefName, hookURL, topts.OnOrg, topts.ParamsRun.Clients.Log)
if topts.DefaultBranch == "" {
topts.DefaultBranch = options.MainBranch
}

repoInfo, err := CreateGiteaRepo(topts.GiteaCNX.Client, topts.Opts.Organization, topts.TargetRepoName, topts.DefaultBranch, hookURL, topts.OnOrg, topts.ParamsRun.Clients.Log)
assert.NilError(t, err)
topts.Opts.Repo = repoInfo.Name
topts.Opts.Organization = repoInfo.Owner.UserName
Expand Down Expand Up @@ -179,7 +190,7 @@ func TestPR(t *testing.T, topts *TestOpts) (context.Context, func()) {
if topts.PullRequest, _, err = topts.GiteaCNX.Client.CreatePullRequest(topts.Opts.Organization, repoInfo.Name, gitea.CreatePullRequestOption{
Title: "Test Pull Request - " + topts.TargetRefName,
Head: topts.TargetRefName,
Base: options.MainBranch,
Base: topts.DefaultBranch,
}); err == nil {
break
}
Expand Down Expand Up @@ -256,8 +267,11 @@ func NewPR(t *testing.T, topts *TestOpts) func() {
topts.TargetNS = topts.TargetRefName
assert.NilError(t, pacrepo.CreateNS(ctx, topts.TargetNS, topts.ParamsRun))
}
if topts.TargetRepoName == "" {
topts.TargetRepoName = topts.TargetRefName
}

repoInfo, err := GetGiteaRepo(topts.GiteaCNX.Client, topts.Opts.Organization, topts.TargetRefName, topts.ParamsRun.Clients.Log)
repoInfo, err := GetGiteaRepo(topts.GiteaCNX.Client, topts.Opts.Organization, topts.TargetRepoName, topts.ParamsRun.Clients.Log)
assert.NilError(t, err)
topts.Opts.Repo = repoInfo.Name
topts.Opts.Organization = repoInfo.Owner.UserName
Expand Down
20 changes: 20 additions & 0 deletions test/testdata/pipelinerun-target-branch-with-comma.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: "\\ .PipelineName //"
annotations:
pipelinesascode.tekton.dev/target-namespace: "\\ .TargetNamespace //"
pipelinesascode.tekton.dev/on-target-branch: "[ branch&#44;with&#44;comma ]"
pipelinesascode.tekton.dev/on-event: "[\\ .TargetEvent //]"
spec:
pipelineSpec:
tasks:
- name: task
taskSpec:
steps:
- name: success
image: registry.access.redhat.com/ubi9/ubi-micro
script: |
echo "I am a such a good booooy"
exit 0

0 comments on commit 4878a24

Please sign in to comment.