Skip to content

Commit

Permalink
feat(tekton): support matching pruns via labels
Browse files Browse the repository at this point in the history
Implement annotation-based PipelineRun to label matching functionality:

- Add `pipelinesascode.tekton.dev/on-label` annotation for label-based
  PipelineRun triggers
- Support label matching on GitHub, Gitea, and GitLab providers
- Implement immediate PipelineRun triggering when labels are added
- Enable re-triggering of PipelineRuns on commit updates with existing labels
- Provide access to Pull Request labels via `{{ pull_request_labels }}`
  dynamic variable

Supported providers:
- GitHub
- Gitea
- GitLab

Limitations:
- Not supported on Bitbucket Cloud and Bitbucket Server

Signed-off-by: Chmouel Boudjnah <[email protected]>
  • Loading branch information
chmouel committed Dec 20, 2024
1 parent 5899d80 commit d701ed2
Show file tree
Hide file tree
Showing 35 changed files with 544 additions and 120 deletions.
35 changes: 18 additions & 17 deletions docs/content/docs/guide/authoringprs.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,24 @@ getting tested. You usually use this with the
[git-clone](https://hub.tekton.dev/tekton/task/git-clone) task to be able to
checkout the code that is being tested.

| Variable | Description | Example | Example Output |
|---------------------|---------------------------------------------------------------------------------------------------|-------------------------------------|------------------------------|
| body | The full payload body (see [below](#using-the-body-and-headers-in-a-pipelines-as-code-parameter)) | `{{body.pull_request.user.email }}` | <[email protected]> |
| event_type | The event type (eg: `pull_request` or `push`) | `{{event_type}}` | pull_request (see the note for Gitops Comments [here]({{< relref "/docs/guide/gitops_commands.md#event-type-annotation-and-dynamic-variables" >}}) ) |
| git_auth_secret | The secret name auto generated with provider token to check out private repos. | `{{git_auth_secret}}` | pac-gitauth-xkxkx |
| headers | The request headers (see [below](#using-the-body-and-headers-in-a-pipelines-as-code-parameter)) | `{{headers['x-github-event']}}` | push |
| pull_request_number | The pull or merge request number, only defined when we are in a `pull_request` event type. | `{{pull_request_number}}` | 1 |
| repo_name | The repository name. | `{{repo_name}}` | pipelines-as-code |
| repo_owner | The repository owner. | `{{repo_owner}}` | openshift-pipelines |
| repo_url | The repository full URL. | `{{repo_url}}` | https:/github.com/repo/owner |
| revision | The commit full sha revision. | `{{revision}}` | 1234567890abcdef |
| sender | The sender username (or accountid on some providers) of the commit. | `{{sender}}` | johndoe |
| source_branch | The branch name where the event come from. | `{{source_branch}}` | main |
| source_url | The source repository URL from which the event come from (same as `repo_url` for push events). | `{{source_url}}` | https:/github.com/repo/owner |
| target_branch | The branch name on which the event targets (same as `source_branch` for push events). | `{{target_branch}}` | main |
| target_namespace | The target namespace where the Repository has matched and the PipelineRun will be created. | `{{target_namespace}}` | my-namespace |
| trigger_comment | The comment triggering the pipelinerun when using a [GitOps command]({{< relref "/docs/guide/running.md#gitops-command-on-pull-or-merge-request" >}}) (like `/test`, `/retest`) | `{{trigger_comment}}` | /merge-pr branch |
| Variable | Description | Example | Example Output |
|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| body | The full payload body (see [below](#using-the-body-and-headers-in-a-pipelines-as-code-parameter)) | `{{body.pull_request.user.email }}` | <[email protected]> |
| event_type | The event type (eg: `pull_request` or `push`) | `{{event_type}}` | pull_request (see the note for Gitops Comments [here]({{< relref "/docs/guide/gitops_commands.md#event-type-annotation-and-dynamic-variables" >}}) ) |
| git_auth_secret | The secret name auto generated with provider token to check out private repos. | `{{git_auth_secret}}` | pac-gitauth-xkxkx |
| headers | The request headers (see [below](#using-the-body-and-headers-in-a-pipelines-as-code-parameter)) | `{{headers['x-github-event']}}` | push |
| pull_request_number | The pull or merge request number, only defined when we are in a `pull_request` event type. | `{{pull_request_number}}` | 1 |
| repo_name | The repository name. | `{{repo_name}}` | pipelines-as-code |
| repo_owner | The repository owner. | `{{repo_owner}}` | openshift-pipelines |
| repo_url | The repository full URL. | `{{repo_url}}` | https:/github.com/repo/owner |
| revision | The commit full sha revision. | `{{revision}}` | 1234567890abcdef |
| sender | The sender username (or accountid on some providers) of the commit. | `{{sender}}` | johndoe |
| source_branch | The branch name where the event come from. | `{{source_branch}}` | main |
| source_url | The source repository URL from which the event come from (same as `repo_url` for push events). | `{{source_url}}` | https:/github.com/repo/owner |
| target_branch | The branch name on which the event targets (same as `source_branch` for push events). | `{{target_branch}}` | main |
| target_namespace | The target namespace where the Repository has matched and the PipelineRun will be created. | `{{target_namespace}}` | my-namespace |
| trigger_comment | The comment triggering the pipelinerun when using a [GitOps command]({{< relref "/docs/guide/running.md#gitops-command-on-pull-or-merge-request" >}}) (like `/test`, `/retest`) | `{{trigger_comment}}` | /merge-pr branch |
| pull_request_labels | The labels of the pull request separated by a newline | `{{pull_request_labels}}` | bugs\nenhancement |

## Matching an event to a PipelineRun

Expand Down
42 changes: 40 additions & 2 deletions docs/content/docs/guide/matchingevents.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ and you have a `Pull Request` changing the files `.tekton/pipelinerun.yaml`,
`on-path-change-ignore` annotation will ignore the `***.md` and `***.yaml`
files.

## Matching a PipelineRun on a regexp in a comment
## Matching a PipelineRun on a Regexp in a comment

{{< tech_preview "Matching PipelineRun on regexp in comments" >}}

Expand Down Expand Up @@ -211,6 +211,44 @@ PipelineRun.

> *NOTE*: The `on-comment` annotation is only supported on GitHub, Gitea and GitLab providers

## Matching PipelineRun to a Pull Request labels

{{< tech_preview "Matching PipelineRun to a Pull-Request label" >}}

Using the annotation `pipelinesascode.tekton.dev/on-label`, you can match a
PipelineRun to a Pull Request label. For example, if you want to match the
PipelineRun `bugs` whenever a Pull Request has the label `bug` or `defect`, you
can use this annotation:

```yaml
metadata:
name: match-bugs-or-defect
annotations:
pipelinesascode.tekton.dev/on-label: [bug, defect]
```

- The `on-label` annotation respects the `pull_request` [Policy]({{< relref
"/docs/guide/policy" >}}) rules.
- This annotation is currently supported only on GitHub, Gitea, and GitLab
providers. Bitbucket Cloud and Bitbucket Server do not support adding labels
to Pull Requests.
- When you add a label to a Pull Request, the corresponding PipelineRun is
triggered immediately, and no other PipelineRun matching the same Pull Request
will be activated.
- If you update the Pull Request by sending a new commit, the PipelineRun
with a matching `on-label` annotation will be triggered again if the label is
still present.
- You can access the `Pull Request` labels with the [dynamic variable]({{<
relref "/docs/guide/authoringprs#dynamic-variables" >}}) `{{ pull_request_labels }}`.
The labels are separated by a Unix newline `\n`.
For example with a shell script you can do this to print them:

```bash
for i in $(echo -e "{{ pull_request_labels }}");do
echo $i
done
```

## Advanced event matching using CEL

If you need to do some advanced matching, `Pipelines-as-Code` supports CEL
Expand Down Expand Up @@ -366,7 +404,7 @@ or close/open the pull request.

{{< /hint >}}

### Matching PipelineRun on request header
### Matching a PipelineRun to a request header

You can do some further filtering on the headers as passed by the Git provider
with the CEL variable `headers`.
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/pipelinesascode/keys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const (
OnComment = pipelinesascode.GroupName + "/on-comment"
OnTargetBranch = pipelinesascode.GroupName + "/on-target-branch"
OnPathChange = pipelinesascode.GroupName + "/on-path-change"
OnLabel = pipelinesascode.GroupName + "/on-label"
OnPathChangeIgnore = pipelinesascode.GroupName + "/on-path-change-ignore"
OnCelExpression = pipelinesascode.GroupName + "/on-cel-expression"
TargetNamespace = pipelinesascode.GroupName + "/target-namespace"
Expand Down
1 change: 1 addition & 0 deletions pkg/customparams/customparams_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func TestProcessTemplates(t *testing.T) {
"target_branch": "",
"target_namespace": "",
"trigger_comment": "",
"pull_request_labels": "",
},
repository: &v1alpha1.Repository{
Spec: v1alpha1.RepositorySpec{},
Expand Down
24 changes: 13 additions & 11 deletions pkg/customparams/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,21 @@ func (p *CustomParams) makeStandardParamsFromEvent(ctx context.Context) (map[str
}
changedFiles := p.getChangedFiles(ctx)
triggerCommentAsSingleLine := strings.ReplaceAll(p.event.TriggerComment, "\n", "\\n")
pullRequestLabels := strings.Join(p.event.PullRequestLabel, "\\n")

return map[string]string{
"revision": p.event.SHA,
"repo_url": repoURL,
"repo_owner": strings.ToLower(p.event.Organization),
"repo_name": strings.ToLower(p.event.Repository),
"target_branch": formatting.SanitizeBranch(p.event.BaseBranch),
"source_branch": formatting.SanitizeBranch(p.event.HeadBranch),
"source_url": p.event.HeadURL,
"sender": strings.ToLower(p.event.Sender),
"target_namespace": p.repo.GetNamespace(),
"event_type": opscomments.EventTypeBackwardCompat(p.eventEmitter, p.repo, p.event.EventType),
"trigger_comment": triggerCommentAsSingleLine,
"revision": p.event.SHA,
"repo_url": repoURL,
"repo_owner": strings.ToLower(p.event.Organization),
"repo_name": strings.ToLower(p.event.Repository),
"target_branch": formatting.SanitizeBranch(p.event.BaseBranch),
"source_branch": formatting.SanitizeBranch(p.event.HeadBranch),
"source_url": p.event.HeadURL,
"sender": strings.ToLower(p.event.Sender),
"target_namespace": p.repo.GetNamespace(),
"event_type": opscomments.EventTypeBackwardCompat(p.eventEmitter, p.repo, p.event.EventType),
"trigger_comment": triggerCommentAsSingleLine,
"pull_request_labels": pullRequestLabels,
}, map[string]interface{}{
"all": changedFiles.All,
"added": changedFiles.Added,
Expand Down
44 changes: 23 additions & 21 deletions pkg/customparams/standard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,32 @@ import (

func TestMakeStandardParamsFromEvent(t *testing.T) {
event := &info.Event{
SHA: "1234567890",
Organization: "Org",
Repository: "Repo",
BaseBranch: "main",
HeadBranch: "foo",
EventType: "pull_request",
Sender: "SENDER",
URL: "https://paris.com",
HeadURL: "https://india.com",
TriggerComment: "/test me\nHelp me obiwan kenobi",
SHA: "1234567890",
Organization: "Org",
Repository: "Repo",
BaseBranch: "main",
HeadBranch: "foo",
EventType: "pull_request",
Sender: "SENDER",
URL: "https://paris.com",
HeadURL: "https://india.com",
TriggerComment: "/test me\nHelp me obiwan kenobi",
PullRequestLabel: []string{"bugs", "enhancements"},
}

result := map[string]string{
"event_type": "pull_request",
"repo_name": "repo",
"repo_owner": "org",
"repo_url": "https://paris.com",
"source_url": "https://india.com",
"revision": "1234567890",
"sender": "sender",
"source_branch": "foo",
"target_branch": "main",
"target_namespace": "myns",
"trigger_comment": "/test me\\nHelp me obiwan kenobi",
"event_type": "pull_request",
"repo_name": "repo",
"repo_owner": "org",
"repo_url": "https://paris.com",
"source_url": "https://india.com",
"revision": "1234567890",
"sender": "sender",
"source_branch": "foo",
"target_branch": "main",
"target_namespace": "myns",
"trigger_comment": "/test me\\nHelp me obiwan kenobi",
"pull_request_labels": "bugs\\nenhancements",
}

repo := &v1alpha1.Repository{
Expand Down
25 changes: 23 additions & 2 deletions pkg/matcher/annotation_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ func MatchPipelinerunByAnnotation(ctx context.Context, logger *zap.SugaredLogger
event.URL,
event.BaseBranch,
event.HeadBranch,
event.TriggerTarget)
event.TriggerTarget,
)

if len(event.PullRequestLabel) > 0 {
infomsg += fmt.Sprintf(", labels=%s", strings.Join(event.PullRequestLabel, "|"))
}

if event.EventType == triggertype.Incoming.String() {
infomsg = fmt.Sprintf("%s, target-pipelinerun=%s", infomsg, event.TargetPipelineRun)
Expand Down Expand Up @@ -248,10 +253,25 @@ func MatchPipelinerunByAnnotation(ctx context.Context, logger *zap.SugaredLogger
if !matched {
continue
}
logger.Infof("Matched pipelinerun with name: %s, annotation PathChange: %q", prName, key)
logger.Infof("matched PipelineRun with name: %s, annotation PathChange: %q", prName, key)
prMatch.Config["path-change"] = key
}

if key, ok := prun.GetObjectMeta().GetAnnotations()[keys.OnLabel]; ok {
matched, err := matchOnAnnotation(key, event.PullRequestLabel, false)
if err != nil {
return matchedPRs, err
}
if !matched {
continue
}
logger.Infof("matched PipelineRun with name: %s, annotation Label: %q", prName, key)
prMatch.Config["label"] = key
} else if event.EventType == string(triggertype.LabelUpdate) {
logger.Infof("label update event, PipelineRun %s does not have a on-label for any of those labels: %s", prName, strings.Join(event.PullRequestLabel, "|"))
continue
}

if key, ok := prun.GetObjectMeta().GetAnnotations()[keys.OnPathChangeIgnore]; ok {
changedFiles, err := vcx.GetFiles(ctx, event)
if err != nil {
Expand Down Expand Up @@ -326,6 +346,7 @@ func matchOnAnnotation(annotations string, eventType []string, branchMatching bo
if v == e {
gotit = v
}

if branchMatching && branchMatch(v, e) {
gotit = v
}
Expand Down
Loading

0 comments on commit d701ed2

Please sign in to comment.