From 64d697eebbd9a5c4eddfe0651b2db849c071cef2 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Wed, 11 Dec 2024 16:24:21 +0100 Subject: [PATCH] feat(tekton): support matching pruns via labels 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 `{{ labels }}` dynamic variable Supported providers: - GitHub - Gitea - GitLab Limitations: - Not supported on Bitbucket Cloud and Bitbucket Server Signed-off-by: Chmouel Boudjnah --- docs/content/docs/guide/authoringprs.md | 35 +++--- docs/content/docs/guide/matchingevents.md | 41 ++++++- pkg/apis/pipelinesascode/keys/keys.go | 1 + pkg/customparams/customparams_test.go | 1 + pkg/customparams/standard.go | 2 + pkg/customparams/standard_test.go | 22 ++-- pkg/matcher/annotation_matcher.go | 25 +++- pkg/matcher/annotation_matcher_test.go | 113 ++++++++++++++++-- pkg/params/info/events.go | 7 +- pkg/params/triggertype/types.go | 11 ++ pkg/policy/policy.go | 4 +- pkg/provider/bitbucketcloud/bitbucket.go | 5 +- pkg/provider/gitea/detect.go | 8 +- pkg/provider/gitea/gitea.go | 14 +-- pkg/provider/gitea/parse_payload.go | 7 ++ pkg/provider/gitea/webhook.go | 3 +- pkg/provider/github/acl.go | 2 +- pkg/provider/github/detect.go | 11 +- pkg/provider/github/github.go | 4 + pkg/provider/github/parse_payload.go | 6 + pkg/provider/github/status.go | 9 +- pkg/provider/gitlab/detect.go | 7 +- pkg/provider/gitlab/detect_test.go | 7 -- pkg/provider/gitlab/gitlab.go | 10 +- pkg/provider/gitlab/parse_payload.go | 11 +- pkg/reconciler/emit_metrics_test.go | 12 +- test/gitea_test.go | 44 +++++++ test/github_pullrequest_test.go | 58 +++++++++ test/gitlab_merge_request_test.go | 78 +++++++++++- test/pkg/gitea/scm.go | 1 + test/pkg/gitea/test.go | 16 +++ test/pkg/github/pr.go | 13 +- .../TestGiteaOnPullRequestLabels.golden | 2 + test/testdata/pipelinerun-on-label.yaml | 23 ++++ 34 files changed, 517 insertions(+), 96 deletions(-) create mode 100644 test/testdata/TestGiteaOnPullRequestLabels.golden create mode 100644 test/testdata/pipelinerun-on-label.yaml diff --git a/docs/content/docs/guide/authoringprs.md b/docs/content/docs/guide/authoringprs.md index e10372433..164578ef9 100644 --- a/docs/content/docs/guide/authoringprs.md +++ b/docs/content/docs/guide/authoringprs.md @@ -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 }}` | | -| 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 }}` | | +| 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 | +| labels | The labels of the pull request separated by a newline | `{{labels}}` | bugs\nenhancement | ## Matching an event to a PipelineRun diff --git a/docs/content/docs/guide/matchingevents.md b/docs/content/docs/guide/matchingevents.md index f7d5f0a61..5434f2c7f 100644 --- a/docs/content/docs/guide/matchingevents.md +++ b/docs/content/docs/guide/matchingevents.md @@ -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" >}} @@ -211,6 +211,43 @@ 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" >}}) `{{ 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 "{{ labels }}");do + echo $i + done + ``` + ## Advanced event matching using CEL If you need to do some advanced matching, `Pipelines-as-Code` supports CEL @@ -366,7 +403,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`. diff --git a/pkg/apis/pipelinesascode/keys/keys.go b/pkg/apis/pipelinesascode/keys/keys.go index e528d6860..f61b729bf 100644 --- a/pkg/apis/pipelinesascode/keys/keys.go +++ b/pkg/apis/pipelinesascode/keys/keys.go @@ -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" diff --git a/pkg/customparams/customparams_test.go b/pkg/customparams/customparams_test.go index 7defe3a8d..37f20e663 100644 --- a/pkg/customparams/customparams_test.go +++ b/pkg/customparams/customparams_test.go @@ -156,6 +156,7 @@ func TestProcessTemplates(t *testing.T) { "target_branch": "", "target_namespace": "", "trigger_comment": "", + "labels": "", }, repository: &v1alpha1.Repository{ Spec: v1alpha1.RepositorySpec{}, diff --git a/pkg/customparams/standard.go b/pkg/customparams/standard.go index e0f1a3d14..1e885d06d 100644 --- a/pkg/customparams/standard.go +++ b/pkg/customparams/standard.go @@ -34,6 +34,7 @@ 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, @@ -47,6 +48,7 @@ func (p *CustomParams) makeStandardParamsFromEvent(ctx context.Context) (map[str "target_namespace": p.repo.GetNamespace(), "event_type": opscomments.EventTypeBackwardCompat(p.eventEmitter, p.repo, p.event.EventType), "trigger_comment": triggerCommentAsSingleLine, + "labels": pullRequestLabels, }, map[string]interface{}{ "all": changedFiles.All, "added": changedFiles.Added, diff --git a/pkg/customparams/standard_test.go b/pkg/customparams/standard_test.go index 9c6f0ae77..f91db4ff5 100644 --- a/pkg/customparams/standard_test.go +++ b/pkg/customparams/standard_test.go @@ -13,16 +13,17 @@ 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{ @@ -37,6 +38,7 @@ func TestMakeStandardParamsFromEvent(t *testing.T) { "target_branch": "main", "target_namespace": "myns", "trigger_comment": "/test me\\nHelp me obiwan kenobi", + "labels": "bugs\\nenhancements", } repo := &v1alpha1.Repository{ diff --git a/pkg/matcher/annotation_matcher.go b/pkg/matcher/annotation_matcher.go index 0801085bb..ee373a404 100644 --- a/pkg/matcher/annotation_matcher.go +++ b/pkg/matcher/annotation_matcher.go @@ -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) @@ -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 { @@ -326,6 +346,7 @@ func matchOnAnnotation(annotations string, eventType []string, branchMatching bo if v == e { gotit = v } + if branchMatching && branchMatch(v, e) { gotit = v } diff --git a/pkg/matcher/annotation_matcher_test.go b/pkg/matcher/annotation_matcher_test.go index 211a0dbc4..5f6e22516 100644 --- a/pkg/matcher/annotation_matcher_test.go +++ b/pkg/matcher/annotation_matcher_test.go @@ -1577,16 +1577,57 @@ func TestMatchPipelinerunByAnnotation(t *testing.T) { args args wantErr bool wantPrName string - wantLog string + wantLog []string }{ { name: "good-match-with-only-one", args: args{ - pruns: []*tektonv1.PipelineRun{pipelineGood}, - runevent: info.Event{TriggerTarget: "pull_request", EventType: "pull_request", BaseBranch: "main"}, + pruns: []*tektonv1.PipelineRun{pipelineGood}, + runevent: info.Event{ + URL: "https://hello/moto", + TriggerTarget: "pull_request", + EventType: "pull_request", + HeadBranch: "source", + BaseBranch: "main", + PullRequestNumber: 10, + }, }, wantErr: false, wantPrName: "pipeline-good", + wantLog: []string{"matching pipelineruns to event: URL=https://hello/moto, target-branch=main, source-branch=source, target-event=pull_request, pull-request=10"}, + }, + { + name: "good-match-on-label", + args: args{ + pruns: []*tektonv1.PipelineRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pipeline-label", + Annotations: map[string]string{ + keys.OnEvent: "[pull_request]", + keys.OnTargetBranch: "[main]", + keys.OnLabel: "[bug]", + }, + }, + }, + pipelineGood, + }, + runevent: info.Event{ + URL: "https://hello/moto", + TriggerTarget: "pull_request", + EventType: "pull_request", + HeadBranch: "source", + BaseBranch: "main", + PullRequestNumber: 10, + PullRequestLabel: []string{"bug", "documentation"}, + }, + }, + wantErr: false, + wantPrName: "pipeline-label", + wantLog: []string{ + "matching pipelineruns to event: URL=https://hello/moto, target-branch=main, source-branch=source, target-event=pull_request, labels=bug|documentation, pull-request=10", + `matched PipelineRun with name: pipeline-label, annotation Label: "[bug]"`, + }, }, { name: "first-one-match-with-two-good-ones", @@ -1613,6 +1654,37 @@ func TestMatchPipelinerunByAnnotation(t *testing.T) { wantErr: false, wantPrName: pipelineCel.GetName(), }, + { + name: "no-match-on-label", + args: args{ + pruns: []*tektonv1.PipelineRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pipeline-label", + Annotations: map[string]string{ + keys.OnEvent: "[pull_request]", + keys.OnTargetBranch: "[main]", + keys.OnLabel: "[bug]", + }, + }, + }, + pipelineGood, + }, + runevent: info.Event{ + URL: "https://hello/moto", + TriggerTarget: "pull_request", + EventType: "pull_request", + HeadBranch: "source", + BaseBranch: "main", + PullRequestNumber: 10, + PullRequestLabel: []string{"documentation"}, + }, + }, + wantErr: false, + wantLog: []string{ + "matching pipelineruns to event: URL=https://hello/moto, target-branch=main, source-branch=source, target-event=pull_request, labels=documentation, pull-request=10", + }, + }, { name: "match-on-comment", args: args{ @@ -1866,10 +1938,18 @@ func TestMatchPipelinerunByAnnotation(t *testing.T) { assert.Assert(t, matches[0].PipelineRun.GetName() == tt.wantPrName, "Pipelinerun hasn't been matched: %+v", matches[0].PipelineRun.GetName(), tt.wantPrName) } - if tt.wantLog != "" { - logmsg := log.TakeAll() - assert.Assert(t, len(logmsg) > 0, "We didn't get any log message") - assert.Assert(t, strings.Contains(logmsg[0].Message, tt.wantLog), logmsg[0].Message, tt.wantLog) + if len(tt.wantLog) > 0 { + assert.Assert(t, log.Len() > 0, "We didn't get any log message") + all := log.TakeAll() + for _, wantLog := range tt.wantLog { + matched := false + for _, entry := range all { + if entry.Message == wantLog { + matched = true + } + } + assert.Assert(t, matched, "We didn't get the expected log message: %s\n%s", wantLog, all) + } } }) } @@ -2297,6 +2377,25 @@ func TestGetTargetBranch(t *testing.T) { expectedBranch string expectedError string }{ + { + name: "Test with pull_request event", + prun: &tektonv1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + keys.OnEvent: "pull_request", + keys.OnTargetBranch: "main", + }, + }, + }, + event: &info.Event{ + TriggerTarget: triggertype.PullRequest, + EventType: triggertype.PullRequest.String(), + BaseBranch: "main", + }, + expectedMatch: true, + expectedEvent: "pull_request", + expectedBranch: "main", + }, { name: "Test with pull_request event", prun: &tektonv1.PipelineRun{ diff --git a/pkg/params/info/events.go b/pkg/params/info/events.go index ade45f046..224c62b56 100644 --- a/pkg/params/info/events.go +++ b/pkg/params/info/events.go @@ -39,9 +39,10 @@ type Event struct { SHAURL string // pretty URL for web browsing for UIs (cli/web) SHATitle string // commit title for UIs - PullRequestNumber int // Pull or Merge Request number - PullRequestTitle string // Title of the pull Request - TriggerComment string // The comment triggering the pipelinerun when using on-comment annotation + PullRequestNumber int // Pull or Merge Request number + PullRequestTitle string // Title of the pull Request + PullRequestLabel []string // Labels of the pull Request + TriggerComment string // The comment triggering the pipelinerun when using on-comment annotation // TODO: move forge specifics to each driver // Github diff --git a/pkg/params/triggertype/types.go b/pkg/params/triggertype/types.go index 39777e7b6..9a9e61994 100644 --- a/pkg/params/triggertype/types.go +++ b/pkg/params/triggertype/types.go @@ -4,6 +4,16 @@ type ( Trigger string ) +// IsPullRequestType all Triggertype that are actually a pull request. +func IsPullRequestType(s string) Trigger { + eventType := s + switch s { + case PullRequest.String(), OkToTest.String(), Retest.String(), Cancel.String(), LabelUpdate.String(): + eventType = PullRequest.String() + } + return Trigger(eventType) +} + func (t Trigger) String() string { return string(t) } @@ -37,6 +47,7 @@ const ( Retest Trigger = "retest" Push Trigger = "push" PullRequest Trigger = "pull_request" + LabelUpdate Trigger = "label_update" Cancel Trigger = "cancel" CheckSuiteRerequested Trigger = "check-suite-rerequested" CheckRunRerequested Trigger = "check-run-rerequested" diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index 9482fbb95..77f9b5cd6 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -45,9 +45,9 @@ func (p *Policy) checkAllowed(ctx context.Context, tType triggertype.Trigger) (R sType = settings.Policy.OkToTest // apply the same policy for PullRequest and comment // we don't support comments on PRs yet but if we do on the future we will need our own policy - case triggertype.PullRequest, triggertype.Comment: + case triggertype.PullRequest, triggertype.Comment, triggertype.LabelUpdate: sType = settings.Policy.PullRequest - // NOTE: not supported yet, will imp if it gets requested and reasonable to implement + // NOTE: not supported yet, will imp if it gets requested and reasonable to implement case triggertype.Push, triggertype.Cancel, triggertype.CheckSuiteRerequested, triggertype.CheckRunRerequested, triggertype.Incoming: return ResultNotSet, "" default: diff --git a/pkg/provider/bitbucketcloud/bitbucket.go b/pkg/provider/bitbucketcloud/bitbucket.go index 65a3d28bc..48bd9a67b 100644 --- a/pkg/provider/bitbucketcloud/bitbucket.go +++ b/pkg/provider/bitbucketcloud/bitbucket.go @@ -112,8 +112,11 @@ func (v *Provider) CreateStatus(_ context.Context, event *info.Event, statusopts if err != nil { return err } + + eventType := triggertype.IsPullRequestType(event.EventType) if statusopts.Conclusion != "STOPPED" && statusopts.Status == "completed" && - statusopts.Text != "" && event.EventType == triggertype.PullRequest.String() { + statusopts.Text != "" && + (eventType == triggertype.PullRequest || event.TriggerTarget == triggertype.PullRequest) { onPr := "" if statusopts.OriginalPipelineRunName != "" { onPr = "/" + statusopts.OriginalPipelineRunName diff --git a/pkg/provider/gitea/detect.go b/pkg/provider/gitea/detect.go index dd5d086b0..45884119d 100644 --- a/pkg/provider/gitea/detect.go +++ b/pkg/provider/gitea/detect.go @@ -11,6 +11,11 @@ import ( "go.uber.org/zap" ) +var ( + pullRequestOpenSyncEvent = []string{"opened", "synchronize", "synchronized", "reopened"} + pullRequestLabelUpdated = "label_updated" +) + // Detect processes event and detect if it is a gitea event, whether to process or reject it // returns (if is a Gitea event, whether to process or reject, logger with event metadata,, error if any occurred). func (v *Provider) Detect(req *http.Request, payload string, logger *zap.SugaredLogger) (bool, bool, *zap.SugaredLogger, string, error) { @@ -51,11 +56,10 @@ func detectTriggerTypeFromPayload(ghEventType string, eventInt any) (triggertype } return "", "invalid payload: no pusher in event" case *giteaStructs.PullRequestPayload: - if provider.Valid(string(event.Action), []string{"opened", "synchronize", "synchronized", "reopened"}) { + if provider.Valid(string(event.Action), append(pullRequestOpenSyncEvent, pullRequestLabelUpdated)) { return triggertype.PullRequest, "" } return "", fmt.Sprintf("pull_request: unsupported action \"%s\"", event.Action) - case *giteaStructs.IssueCommentPayload: if event.Action == "created" && event.Issue.PullRequest != nil && diff --git a/pkg/provider/gitea/gitea.go b/pkg/provider/gitea/gitea.go index f46f525f9..58e4adf2a 100644 --- a/pkg/provider/gitea/gitea.go +++ b/pkg/provider/gitea/gitea.go @@ -174,16 +174,12 @@ func (v *Provider) createStatusCommit(event *info.Event, pacopts *info.PacOpts, if _, _, err := v.Client.CreateStatus(event.Organization, event.Repository, event.SHA, gStatus); err != nil { return err } - eventType := event.EventType - if eventType == triggertype.OkToTest.String() || eventType == triggertype.Retest.String() || - eventType == triggertype.Cancel.String() { - eventType = triggertype.PullRequest.String() - } - if opscomments.IsAnyOpsEventType(eventType) { - eventType = triggertype.PullRequest.String() - } - if status.Text != "" && (eventType == triggertype.PullRequest.String() || event.TriggerTarget == triggertype.PullRequest) { + eventType := triggertype.IsPullRequestType(event.EventType) + if opscomments.IsAnyOpsEventType(eventType.String()) { + eventType = triggertype.PullRequest + } + if status.Text != "" && (eventType == triggertype.PullRequest || event.TriggerTarget == triggertype.PullRequest) { status.Text = strings.ReplaceAll(strings.TrimSpace(status.Text), "
", "\n") _, _, err := v.Client.CreateIssueComment(event.Organization, event.Repository, int64(event.PullRequestNumber), gitea.CreateIssueCommentOption{ diff --git a/pkg/provider/gitea/parse_payload.go b/pkg/provider/gitea/parse_payload.go index 3e2db9228..5cc424378 100644 --- a/pkg/provider/gitea/parse_payload.go +++ b/pkg/provider/gitea/parse_payload.go @@ -11,6 +11,7 @@ import ( "github.com/openshift-pipelines/pipelines-as-code/pkg/params" "github.com/openshift-pipelines/pipelines-as-code/pkg/params/info" "github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype" + "github.com/openshift-pipelines/pipelines-as-code/pkg/provider" ) func (v *Provider) ParsePayload(_ context.Context, _ *params.Run, request *http.Request, @@ -50,6 +51,12 @@ func (v *Provider) ParsePayload(_ context.Context, _ *params.Run, request *http. processedEvent.Repository = gitEvent.Repository.Name processedEvent.TriggerTarget = triggertype.PullRequest processedEvent.EventType = triggertype.PullRequest.String() + if provider.Valid(string(gitEvent.Action), []string{pullRequestLabelUpdated}) { + processedEvent.EventType = string(triggertype.LabelUpdate) + } + for _, label := range gitEvent.PullRequest.Labels { + processedEvent.PullRequestLabel = append(processedEvent.PullRequestLabel, label.Name) + } case *giteaStructs.PushPayload: processedEvent = info.NewEvent() processedEvent.SHA = gitEvent.HeadCommit.ID diff --git a/pkg/provider/gitea/webhook.go b/pkg/provider/gitea/webhook.go index 8694076c0..456b81f1e 100644 --- a/pkg/provider/gitea/webhook.go +++ b/pkg/provider/gitea/webhook.go @@ -27,6 +27,7 @@ const ( EventTypePullRequest whEventType = "pull_request" EventTypePullRequestApproved whEventType = "pull_request_approved" EventTypePullRequestRejected whEventType = "pull_request_rejected" + EventTypePullRequestLabel whEventType = "pull_request_label" EventTypePullRequestComment whEventType = "pull_request_comment" EventTypePullRequestSync whEventType = "pull_request_sync" ) @@ -51,7 +52,7 @@ func parseWebhook(eventType whEventType, payload []byte) (event interface{}, err event = &giteaStructs.ReleasePayload{} case EventTypePullRequestComment: event = &giteaStructs.IssueCommentPayload{} - case EventTypePullRequest, EventTypePullRequestApproved, EventTypePullRequestSync, EventTypePullRequestRejected: + case EventTypePullRequest, EventTypePullRequestApproved, EventTypePullRequestSync, EventTypePullRequestRejected, EventTypePullRequestLabel: event = &giteaStructs.PullRequestPayload{} default: return nil, fmt.Errorf("unexpected event type: %s", eventType) diff --git a/pkg/provider/github/acl.go b/pkg/provider/github/acl.go index 5ad54d53e..babe3476d 100644 --- a/pkg/provider/github/acl.go +++ b/pkg/provider/github/acl.go @@ -78,7 +78,7 @@ func (v *Provider) IsAllowed(ctx context.Context, event *info.Event) (bool, erro } // Try to detect a policy rule allowing this - tType, _ := detectTriggerTypeFromPayload("", event.Event) + tType, _ := v.detectTriggerTypeFromPayload("", event.Event) policyAllowed, policyReason := aclPolicy.IsAllowed(ctx, tType) switch policyAllowed { diff --git a/pkg/provider/github/detect.go b/pkg/provider/github/detect.go index eeb0563f5..51be80164 100644 --- a/pkg/provider/github/detect.go +++ b/pkg/provider/github/detect.go @@ -11,6 +11,11 @@ import ( "go.uber.org/zap" ) +var ( + pullRequestOpenSyncEvent = []string{"opened", "synchronize", "synchronized", "reopened"} + pullRequestLabelEvent = []string{"labeled"} +) + // Detect processes event and detect if it is a github event, whether to process or reject it // returns (if is a GH event, whether to process or reject, error if any occurred). func (v *Provider) Detect(req *http.Request, payload string, logger *zap.SugaredLogger) (bool, bool, *zap.SugaredLogger, string, error) { @@ -40,7 +45,7 @@ func (v *Provider) Detect(req *http.Request, payload string, logger *zap.Sugared } _ = json.Unmarshal([]byte(payload), &eventInt) - eType, errReason := detectTriggerTypeFromPayload(eventType, eventInt) + eType, errReason := v.detectTriggerTypeFromPayload(eventType, eventInt) if eType != "" { return setLoggerAndProceed(true, "", nil) } @@ -51,7 +56,7 @@ func (v *Provider) Detect(req *http.Request, payload string, logger *zap.Sugared // detectTriggerTypeFromPayload will detect the event type from the payload, // filtering out the events that are not supported. // first arg will get the event type and the second one will get an error string explaining why it's not supported. -func detectTriggerTypeFromPayload(ghEventType string, eventInt any) (triggertype.Trigger, string) { +func (v *Provider) detectTriggerTypeFromPayload(ghEventType string, eventInt any) (triggertype.Trigger, string) { switch event := eventInt.(type) { case *github.PushEvent: if event.GetPusher() != nil { @@ -59,7 +64,7 @@ func detectTriggerTypeFromPayload(ghEventType string, eventInt any) (triggertype } return "", "no pusher in payload" case *github.PullRequestEvent: - if provider.Valid(event.GetAction(), []string{"opened", "synchronize", "synchronized", "reopened"}) { + if provider.Valid(event.GetAction(), pullRequestOpenSyncEvent) || provider.Valid(event.GetAction(), pullRequestLabelEvent) { return triggertype.PullRequest, "" } return "", fmt.Sprintf("pull_request: unsupported action \"%s\"", event.GetAction()) diff --git a/pkg/provider/github/github.go b/pkg/provider/github/github.go index f504c0d97..dbcb42927 100644 --- a/pkg/provider/github/github.go +++ b/pkg/provider/github/github.go @@ -446,6 +446,10 @@ func (v *Provider) getPullRequest(ctx context.Context, runevent *info.Event) (*i runevent.EventType = triggertype.PullRequest.String() } + for _, label := range pr.Labels { + runevent.PullRequestLabel = append(runevent.PullRequestLabel, label.GetName()) + } + v.RepositoryIDs = []int64{ pr.GetBase().GetRepo().GetID(), } diff --git a/pkg/provider/github/parse_payload.go b/pkg/provider/github/parse_payload.go index 586e67a11..91b01df90 100644 --- a/pkg/provider/github/parse_payload.go +++ b/pkg/provider/github/parse_payload.go @@ -284,6 +284,9 @@ func (v *Provider) processEvent(ctx context.Context, event *info.Event, eventInt processedEvent.HeadURL = gitEvent.GetPullRequest().Head.GetRepo().GetHTMLURL() processedEvent.Sender = gitEvent.GetPullRequest().GetUser().GetLogin() processedEvent.EventType = event.EventType + if gitEvent.Action != nil && provider.Valid(*gitEvent.Action, pullRequestLabelEvent) { + processedEvent.EventType = string(triggertype.LabelUpdate) + } processedEvent.PullRequestNumber = gitEvent.GetPullRequest().GetNumber() processedEvent.PullRequestTitle = gitEvent.GetPullRequest().GetTitle() // getting the repository ids of the base and head of the pull request @@ -291,6 +294,9 @@ func (v *Provider) processEvent(ctx context.Context, event *info.Event, eventInt v.RepositoryIDs = []int64{ gitEvent.GetPullRequest().GetBase().GetRepo().GetID(), } + for _, label := range gitEvent.GetPullRequest().Labels { + processedEvent.PullRequestLabel = append(processedEvent.PullRequestLabel, label.GetName()) + } default: return nil, errors.New("this event is not supported") } diff --git a/pkg/provider/github/status.go b/pkg/provider/github/status.go index 53397dac8..429ca29db 100644 --- a/pkg/provider/github/status.go +++ b/pkg/provider/github/status.go @@ -13,6 +13,7 @@ import ( "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys" "github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction" kstatus "github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction/status" + "github.com/openshift-pipelines/pipelines-as-code/pkg/opscomments" "github.com/openshift-pipelines/pipelines-as-code/pkg/params/info" "github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype" "github.com/openshift-pipelines/pipelines-as-code/pkg/provider" @@ -321,7 +322,13 @@ func (v *Provider) createStatusCommit(ctx context.Context, runevent *info.Event, runevent.Organization, runevent.Repository, runevent.SHA, ghstatus); err != nil { return err } - if (status.Status == "completed" || (status.Status == "queued" && status.Title == "Pending approval")) && status.Text != "" && runevent.EventType == triggertype.PullRequest.String() { + eventType := triggertype.IsPullRequestType(runevent.EventType) + if opscomments.IsAnyOpsEventType(eventType.String()) { + eventType = triggertype.PullRequest + } + if (status.Status == "completed" || + (status.Status == "queued" && status.Title == "Pending approval")) && + status.Text != "" && eventType == triggertype.PullRequest { _, _, err = v.Client.Issues.CreateComment(ctx, runevent.Organization, runevent.Repository, runevent.PullRequestNumber, &github.IssueComment{ diff --git a/pkg/provider/gitlab/detect.go b/pkg/provider/gitlab/detect.go index 5a8bf7518..1ace8ca10 100644 --- a/pkg/provider/gitlab/detect.go +++ b/pkg/provider/gitlab/detect.go @@ -37,12 +37,7 @@ func (v *Provider) Detect(req *http.Request, payload string, logger *zap.Sugared switch gitEvent := eventInt.(type) { case *gitlab.MergeEvent: - // on a MR Update only react when there is Oldrev set, since this means - // there is a Push of commit in there - if gitEvent.ObjectAttributes.Action == "update" && gitEvent.ObjectAttributes.OldRev != "" { - return setLoggerAndProceed(true, "", nil) - } - if provider.Valid(gitEvent.ObjectAttributes.Action, []string{"open", "reopen"}) { + if provider.Valid(gitEvent.ObjectAttributes.Action, []string{"open", "reopen", "update"}) { return setLoggerAndProceed(true, "", nil) } return setLoggerAndProceed(false, fmt.Sprintf("not a merge event we care about: \"%s\"", diff --git a/pkg/provider/gitlab/detect_test.go b/pkg/provider/gitlab/detect_test.go index dd0592cd1..10b6e8022 100644 --- a/pkg/provider/gitlab/detect_test.go +++ b/pkg/provider/gitlab/detect_test.go @@ -70,13 +70,6 @@ func TestProvider_Detect(t *testing.T) { isGL: true, processReq: true, }, - { - name: "bad/mergeRequest update Event with no commit", - event: sample.MREventAsJSON("update", ``), - eventType: gitlab.EventTypeMergeRequest, - isGL: true, - processReq: false, - }, { name: "good/note event", event: sample.NoteEventAsJSON("abc"), diff --git a/pkg/provider/gitlab/gitlab.go b/pkg/provider/gitlab/gitlab.go index 4ca9fdf8a..2247145dd 100644 --- a/pkg/provider/gitlab/gitlab.go +++ b/pkg/provider/gitlab/gitlab.go @@ -40,6 +40,8 @@ const ( noClientErrStr = `no gitlab client has been initialized, exiting... (hint: did you forget setting a secret on your repo?)` ) +var anyMergeRequestEventType = []string{"Merge Request", "MergeRequest"} + var _ provider.Interface = (*Provider)(nil) type Provider struct { @@ -217,10 +219,12 @@ func (v *Provider) CreateStatus(_ context.Context, event *info.Event, statusOpts "cannot set status with the GitLab token because of: "+err.Error()) } + eventType := triggertype.IsPullRequestType(event.EventType) + if opscomments.IsAnyOpsEventType(eventType.String()) { + eventType = triggertype.PullRequest + } // only add a note when we are on a MR - if event.EventType == triggertype.PullRequest.String() || - event.EventType == "Merge_Request" || event.EventType == "Merge Request" || - opscomments.IsAnyOpsEventType(event.EventType) { + if eventType == triggertype.PullRequest || provider.Valid(event.EventType, anyMergeRequestEventType) { mopt := &gitlab.CreateMergeRequestNoteOptions{Body: gitlab.Ptr(body)} _, _, err := v.Client.Notes.CreateMergeRequestNote(event.TargetProjectID, event.PullRequestNumber, mopt) return err diff --git a/pkg/provider/gitlab/parse_payload.go b/pkg/provider/gitlab/parse_payload.go index 31464fec1..e44221032 100644 --- a/pkg/provider/gitlab/parse_payload.go +++ b/pkg/provider/gitlab/parse_payload.go @@ -56,10 +56,19 @@ func (v *Provider) ParsePayload(_ context.Context, _ *params.Run, request *http. v.pathWithNamespace = gitEvent.ObjectAttributes.Target.PathWithNamespace processedEvent.Organization, processedEvent.Repository = getOrgRepo(v.pathWithNamespace) - processedEvent.TriggerTarget = triggertype.PullRequest processedEvent.SourceProjectID = gitEvent.ObjectAttributes.SourceProjectID processedEvent.TargetProjectID = gitEvent.Project.ID + + processedEvent.TriggerTarget = triggertype.PullRequest processedEvent.EventType = strings.ReplaceAll(event, " Hook", "") + + // This is a label update, like adding or removing a label from a MR. + if gitEvent.Changes.Labels.Current != nil { + processedEvent.EventType = triggertype.LabelUpdate.String() + } + for _, label := range gitEvent.Labels { + processedEvent.PullRequestLabel = append(processedEvent.PullRequestLabel, label.Title) + } case *gitlab.TagEvent: // GitLab sends same event for both Tag creation and deletion i.e. "Tag Push Hook". // if gitEvent.After is containing all zeros and gitEvent.CheckoutSHA is empty diff --git a/pkg/reconciler/emit_metrics_test.go b/pkg/reconciler/emit_metrics_test.go index 0a952738e..9b5a23259 100644 --- a/pkg/reconciler/emit_metrics_test.go +++ b/pkg/reconciler/emit_metrics_test.go @@ -132,7 +132,7 @@ func TestCalculatePipelineRunDuration(t *testing.T) { conditionType: apis.ConditionSucceeded, status: corev1.ConditionTrue, reason: tektonv1.PipelineRunReasonSuccessful.String(), - completionTime: metav1.NewTime(startTime.Time.Add(time.Minute)), + completionTime: metav1.NewTime(startTime.Add(time.Minute)), tags: map[string]string{ "namespace": "pac-ns", "reason": tektonv1.PipelineRunReasonSuccessful.String(), @@ -148,7 +148,7 @@ func TestCalculatePipelineRunDuration(t *testing.T) { conditionType: apis.ConditionSucceeded, status: corev1.ConditionTrue, reason: tektonv1.PipelineRunReasonCompleted.String(), - completionTime: metav1.NewTime(startTime.Time.Add(time.Minute)), + completionTime: metav1.NewTime(startTime.Add(time.Minute)), tags: map[string]string{ "namespace": "pac-ns", "reason": tektonv1.PipelineRunReasonCompleted.String(), @@ -164,7 +164,7 @@ func TestCalculatePipelineRunDuration(t *testing.T) { conditionType: apis.ConditionSucceeded, status: corev1.ConditionFalse, reason: tektonv1.PipelineRunReasonFailed.String(), - completionTime: metav1.NewTime(startTime.Time.Add(2 * time.Minute)), + completionTime: metav1.NewTime(startTime.Add(2 * time.Minute)), tags: map[string]string{ "namespace": "pac-ns", "reason": tektonv1.PipelineRunReasonFailed.String(), @@ -180,7 +180,7 @@ func TestCalculatePipelineRunDuration(t *testing.T) { conditionType: apis.ConditionSucceeded, status: corev1.ConditionFalse, reason: tektonv1.PipelineRunReasonCancelled.String(), - completionTime: metav1.NewTime(startTime.Time.Add(2 * time.Second)), + completionTime: metav1.NewTime(startTime.Add(2 * time.Second)), tags: map[string]string{ "namespace": "pac-ns", "reason": tektonv1.PipelineRunReasonCancelled.String(), @@ -196,7 +196,7 @@ func TestCalculatePipelineRunDuration(t *testing.T) { conditionType: apis.ConditionSucceeded, status: corev1.ConditionFalse, reason: tektonv1.PipelineRunReasonTimedOut.String(), - completionTime: metav1.NewTime(startTime.Time.Add(10 * time.Minute)), + completionTime: metav1.NewTime(startTime.Add(10 * time.Minute)), tags: map[string]string{ "namespace": "pac-ns", "reason": tektonv1.PipelineRunReasonTimedOut.String(), @@ -212,7 +212,7 @@ func TestCalculatePipelineRunDuration(t *testing.T) { conditionType: apis.ConditionSucceeded, status: corev1.ConditionFalse, reason: tektonv1.PipelineRunReasonCouldntGetPipeline.String(), - completionTime: metav1.NewTime(startTime.Time.Add(time.Second)), + completionTime: metav1.NewTime(startTime.Add(time.Second)), tags: map[string]string{ "namespace": "pac-ns", "reason": tektonv1.PipelineRunReasonCouldntGetPipeline.String(), diff --git a/test/gitea_test.go b/test/gitea_test.go index 1628a2401..c864578f8 100644 --- a/test/gitea_test.go +++ b/test/gitea_test.go @@ -759,6 +759,50 @@ func TestGiteaErrorSnippet(t *testing.T) { tgitea.WaitForPullRequestCommentMatch(t, topts) } +func TestGiteaOnPullRequestLabels(t *testing.T) { + prName := "on-label" + topts := &tgitea.TestOpts{ + TargetEvent: triggertype.PullRequest.String(), + YAMLFiles: map[string]string{ + fmt.Sprintf(".tekton/%s.yaml", prName): "testdata/pipelinerun-on-label.yaml", + }, + ExpectEvents: false, + CheckForNumberStatus: 0, + } + _, f := tgitea.TestPR(t, topts) + defer f() + + tgitea.AddLabelToIssue(t, topts, "bug") + + waitOpts := twait.Opts{ + RepoName: topts.TargetNS, + Namespace: topts.TargetNS, + MinNumberStatus: 1, // 1 means 2 🙃 + PollTimeout: twait.DefaultTimeout, + TargetSHA: topts.PullRequest.Head.Sha, + } + _, err := twait.UntilRepositoryUpdated(context.Background(), topts.ParamsRun.Clients, waitOpts) + assert.NilError(t, err) + + topts.CheckForStatus = "success" + tgitea.WaitForStatus(t, topts, topts.TargetRefName, "", true) + + prs, err := topts.ParamsRun.Clients.Tekton.TektonV1().PipelineRuns(topts.TargetNS).List(context.Background(), metav1.ListOptions{}) + assert.NilError(t, err) + + assert.Equal(t, len(prs.Items), 1, "should have only one pipelinerun, but we have: %d", len(prs.Items)) + + repo, err := topts.ParamsRun.Clients.PipelineAsCode.PipelinesascodeV1alpha1().Repositories(topts.TargetNS).Get(context.Background(), topts.TargetNS, metav1.GetOptions{}) + assert.NilError(t, err) + twait.GoldenPodLog(context.Background(), t, topts.ParamsRun, topts.TargetNS, + fmt.Sprintf("tekton.dev/pipelineRun=%s,tekton.dev/pipelineTask=task", repo.Status[0].PipelineRunName), + "step-success", strings.ReplaceAll(fmt.Sprintf("%s.golden", t.Name()), "/", "-"), 2) + + // Make sure the on-label pr has triggered and post status + topts.Regexp = regexp.MustCompile(fmt.Sprintf("Pipelines as Code CI/%s.* has successfully validated your commit", prName)) + tgitea.WaitForPullRequestCommentMatch(t, topts) +} + func TestGiteaErrorSnippetWithSecret(t *testing.T) { var err error ctx := context.Background() diff --git a/test/github_pullrequest_test.go b/test/github_pullrequest_test.go index 106a7c140..e2ebb26de 100644 --- a/test/github_pullrequest_test.go +++ b/test/github_pullrequest_test.go @@ -14,6 +14,8 @@ import ( "github.com/google/go-github/v66/github" "github.com/openshift-pipelines/pipelines-as-code/pkg/opscomments" + "github.com/openshift-pipelines/pipelines-as-code/pkg/params/settings" + "github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype" tgithub "github.com/openshift-pipelines/pipelines-as-code/test/pkg/github" "github.com/openshift-pipelines/pipelines-as-code/test/pkg/options" twait "github.com/openshift-pipelines/pipelines-as-code/test/pkg/wait" @@ -69,6 +71,62 @@ func TestGithubPullRequestMatchOnCEL(t *testing.T) { defer g.TearDown(ctx, t) } +func TestGithubPullRequestOnLabel(t *testing.T) { + ctx := context.Background() + g := &tgithub.PRTest{ + Label: "Github On Label", + YamlFiles: []string{"testdata/pipelinerun-on-label.yaml"}, + NoStatusCheck: true, + } + g.RunPullRequest(ctx, t) + defer g.TearDown(ctx, t) + + // wait a bit that GitHub processed or we will get double events + time.Sleep(5 * time.Second) + + g.Cnx.Clients.Log.Infof("Creating a label bug on PullRequest") + _, _, err := g.Provider.Client.Issues.AddLabelsToIssue(ctx, + g.Options.Organization, + g.Options.Repo, g.PRNumber, + []string{"bug"}) + assert.NilError(t, err) + + sopt := twait.SuccessOpt{ + Title: g.CommitTitle, + OnEvent: triggertype.LabelUpdate.String(), + TargetNS: g.TargetNamespace, + NumberofPRMatch: len(g.YamlFiles), + SHA: g.SHA, + } + twait.Succeeded(ctx, t, g.Cnx, g.Options, sopt) + + opt := github.ListOptions{} + res := &github.ListCheckRunsResults{} + resp := &github.Response{} + counter := 0 + for { + res, resp, err = g.Provider.Client.Checks.ListCheckRunsForRef(ctx, g.Options.Organization, g.Options.Repo, g.SHA, &github.ListCheckRunsOptions{ + AppID: g.Provider.ApplicationID, + ListOptions: opt, + }) + assert.NilError(t, err) + assert.Equal(t, resp.StatusCode, 200) + if len(res.CheckRuns) > 0 { + break + } + g.Cnx.Clients.Log.Infof("Waiting for the check run to be created") + if counter > 10 { + t.Errorf("Check run not created after 10 tries") + break + } + time.Sleep(5 * time.Second) + } + assert.Equal(t, len(res.CheckRuns), 1) + expected := fmt.Sprintf("%s / %s", settings.PACApplicationNameDefaultValue, "pipelinerun-on-label-") + checkName := res.CheckRuns[0].GetName() + assert.Assert(t, strings.HasPrefix(checkName, expected), "checkName %s != expected %s", checkName, expected) +} + func TestGithubPullRequestCELMatchOnTitle(t *testing.T) { ctx := context.Background() g := &tgithub.PRTest{ diff --git a/test/gitlab_merge_request_test.go b/test/gitlab_merge_request_test.go index 394865352..ab1c4cdf3 100644 --- a/test/gitlab_merge_request_test.go +++ b/test/gitlab_merge_request_test.go @@ -9,6 +9,7 @@ import ( "net/http" "regexp" "testing" + "time" "github.com/google/go-github/v66/github" "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys" @@ -73,12 +74,6 @@ func TestGitlabMergeRequest(t *testing.T) { runcnx.Clients.Log.Infof("MergeRequest %s/-/merge_requests/%d has been created", projectinfo.WebURL, mrID) defer tgitlab.TearDown(ctx, t, runcnx, glprovider, mrID, targetRefName, targetNS, opts.ProjectID) - // updating labels to test if we skip them, this used to create multiple PRs - _, _, err = glprovider.Client.MergeRequests.UpdateMergeRequest(opts.ProjectID, mrID, &clientGitlab.UpdateMergeRequestOptions{ - Labels: &clientGitlab.LabelOptions{"hello-label"}, - }) - assert.NilError(t, err) - // Send another Push to make an update and make sure we react to it entries, err = payload.GetEntries(map[string]string{ "hello-world.yaml": "testdata/pipelinerun.yaml", @@ -109,6 +104,7 @@ func TestGitlabMergeRequest(t *testing.T) { Body: clientGitlab.Ptr("/retest"), }) assert.NilError(t, err) + sopt = twait.SuccessOpt{ Title: commitTitle, OnEvent: opscomments.RetestAllCommentEventType.String(), @@ -131,6 +127,76 @@ func TestGitlabMergeRequest(t *testing.T) { assert.Equal(t, 6, successCommentsPost) } +func TestGitlabOnLabel(t *testing.T) { + prName := "on-label" + targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns") + ctx := context.Background() + runcnx, opts, glprovider, err := tgitlab.Setup(ctx) + assert.NilError(t, err) + ctx, err = cctx.GetControllerCtxInfo(ctx, runcnx) + assert.NilError(t, err) + runcnx.Clients.Log.Info("Testing with Gitlab") + + projectinfo, resp, err := glprovider.Client.Projects.GetProject(opts.ProjectID, nil) + assert.NilError(t, err) + if resp != nil && resp.StatusCode == http.StatusNotFound { + t.Errorf("Repository %s not found in %s", opts.Organization, opts.Repo) + } + + err = tgitlab.CreateCRD(ctx, projectinfo, runcnx, targetNS, nil) + assert.NilError(t, err) + + entries, err := payload.GetEntries(map[string]string{ + fmt.Sprintf(".tekton/%s.yaml", prName): "testdata/pipelinerun-on-label.yaml", + }, targetNS, projectinfo.DefaultBranch, + triggertype.PullRequest.String(), map[string]string{}) + assert.NilError(t, err) + targetRefName := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test") + gitCloneURL, err := scm.MakeGitCloneURL(projectinfo.WebURL, opts.UserName, opts.Password) + assert.NilError(t, err) + commitTitle := "Committing files from test on " + targetRefName + scmOpts := &scm.Opts{ + GitURL: gitCloneURL, + CommitTitle: commitTitle, + Log: runcnx.Clients.Log, + WebURL: projectinfo.WebURL, + TargetRefName: targetRefName, + BaseRefName: projectinfo.DefaultBranch, + } + scm.PushFilesToRefGit(t, scmOpts, entries) + runcnx.Clients.Log.Infof("Branch %s has been created and pushed with files", targetRefName) + + mrTitle := "TestMergeRequest - " + targetRefName + mrID, err := tgitlab.CreateMR(glprovider.Client, opts.ProjectID, targetRefName, projectinfo.DefaultBranch, mrTitle) + assert.NilError(t, err) + runcnx.Clients.Log.Infof("MergeRequest %s/-/merge_requests/%d has been created", projectinfo.WebURL, mrID) + defer tgitlab.TearDown(ctx, t, runcnx, glprovider, mrID, targetRefName, targetNS, opts.ProjectID) + + runcnx.Clients.Log.Infof("waiting 5 seconds until we make sure nothing happened") + time.Sleep(5 * time.Second) + prsNew, err := runcnx.Clients.Tekton.TektonV1().PipelineRuns(targetNS).List(ctx, metav1.ListOptions{}) + assert.NilError(t, err) + assert.Assert(t, len(prsNew.Items) == 0) + + // now add a Label + mr, _, err := glprovider.Client.MergeRequests.UpdateMergeRequest(opts.ProjectID, mrID, &clientGitlab.UpdateMergeRequestOptions{ + Labels: &clientGitlab.LabelOptions{"bug"}, + }) + assert.NilError(t, err) + + waitOpts := twait.Opts{ + RepoName: targetNS, + Namespace: targetNS, + MinNumberStatus: 1, + PollTimeout: twait.DefaultTimeout, + TargetSHA: mr.SHA, + } + repo, err := twait.UntilRepositoryUpdated(ctx, runcnx.Clients, waitOpts) + assert.NilError(t, err) + assert.Assert(t, len(repo.Status) > 0) + assert.Equal(t, *repo.Status[0].EventType, triggertype.LabelUpdate.String()) +} + func TestGitlabOnComment(t *testing.T) { triggerComment := "/hello-world" targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns") diff --git a/test/pkg/gitea/scm.go b/test/pkg/gitea/scm.go index 393a3cd45..b26812f29 100644 --- a/test/pkg/gitea/scm.go +++ b/test/pkg/gitea/scm.go @@ -134,6 +134,7 @@ func CreateGiteaRepo(giteaClient *gitea.Client, user, name, defaultBranch, hookU Name: name, Description: "This is a repo it's a wonderful thing", AutoInit: true, + IssueLabels: "Default", DefaultBranch: defaultBranch, }) } diff --git a/test/pkg/gitea/test.go b/test/pkg/gitea/test.go index 9c610a1f2..e352244f7 100644 --- a/test/pkg/gitea/test.go +++ b/test/pkg/gitea/test.go @@ -72,6 +72,22 @@ func PostCommentOnPullRequest(t *testing.T, topt *TestOpts, body string) { assert.NilError(t, err) } +func AddLabelToIssue(t *testing.T, topt *TestOpts, label string) { + var targetID int64 + allLabels, _, err := topt.GiteaCNX.Client.ListRepoLabels(topt.Opts.Organization, topt.Opts.Repo, gitea.ListLabelsOptions{}) + assert.NilError(t, err) + for _, l := range allLabels { + if l.Name == label { + targetID = l.ID + } + } + + opt := gitea.IssueLabelsOption{Labels: []int64{targetID}} + _, _, err = topt.GiteaCNX.Client.AddIssueLabels(topt.Opts.Organization, topt.Opts.Repo, topt.PullRequest.Index, opt) + assert.NilError(t, err) + topt.ParamsRun.Clients.Log.Infof("Added label \"%s\" to %s", label, topt.PullRequest.HTMLURL) +} + // TestPR will test the pull request event and grab comments from the PR. func TestPR(t *testing.T, topts *TestOpts) (context.Context, func()) { ctx := context.Background() diff --git a/test/pkg/github/pr.go b/test/pkg/github/pr.go index 95db6bfb8..543dd433a 100644 --- a/test/pkg/github/pr.go +++ b/test/pkg/github/pr.go @@ -118,6 +118,7 @@ type PRTest struct { PRNumber int SHA string Logger *zap.SugaredLogger + CommitTitle string } func (g *PRTest) RunPullRequest(ctx context.Context, t *testing.T) { @@ -127,12 +128,12 @@ func (g *PRTest) RunPullRequest(ctx context.Context, t *testing.T) { assert.NilError(t, err) g.Logger = runcnx.Clients.Log - logmsg := fmt.Sprintf("Testing %s with Github APPS integration on %s", g.Label, targetNS) - g.Logger.Info(logmsg) + g.CommitTitle = fmt.Sprintf("Testing %s with Github APPS integration on %s", g.Label, targetNS) + g.Logger.Info(g.CommitTitle) repoinfo, resp, err := ghcnx.Client.Repositories.Get(ctx, opts.Organization, opts.Repo) assert.NilError(t, err) - if resp != nil && resp.Response.StatusCode == http.StatusNotFound { + if resp != nil && resp.StatusCode == http.StatusNotFound { t.Errorf("Repository %s not found in %s", opts.Organization, opts.Repo) } @@ -151,17 +152,17 @@ func (g *PRTest) RunPullRequest(ctx context.Context, t *testing.T) { targetRefName := fmt.Sprintf("refs/heads/%s", names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test")) - sha, vref, err := PushFilesToRef(ctx, ghcnx.Client, logmsg, repoinfo.GetDefaultBranch(), targetRefName, + sha, vref, err := PushFilesToRef(ctx, ghcnx.Client, g.CommitTitle, repoinfo.GetDefaultBranch(), targetRefName, opts.Organization, opts.Repo, entries) assert.NilError(t, err) g.Logger.Infof("Commit %s has been created and pushed to %s", sha, vref.GetURL()) number, err := PRCreate(ctx, runcnx, ghcnx, opts.Organization, - opts.Repo, targetRefName, repoinfo.GetDefaultBranch(), logmsg) + opts.Repo, targetRefName, repoinfo.GetDefaultBranch(), g.CommitTitle) assert.NilError(t, err) if !g.NoStatusCheck { sopt := wait.SuccessOpt{ - Title: logmsg, + Title: g.CommitTitle, OnEvent: triggertype.PullRequest.String(), TargetNS: targetNS, NumberofPRMatch: len(g.YamlFiles), diff --git a/test/testdata/TestGiteaOnPullRequestLabels.golden b/test/testdata/TestGiteaOnPullRequestLabels.golden new file mode 100644 index 000000000..c31d3e9c7 --- /dev/null +++ b/test/testdata/TestGiteaOnPullRequestLabels.golden @@ -0,0 +1,2 @@ +Labels on PR: +bug diff --git a/test/testdata/pipelinerun-on-label.yaml b/test/testdata/pipelinerun-on-label.yaml new file mode 100644 index 000000000..64e77a43f --- /dev/null +++ b/test/testdata/pipelinerun-on-label.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: "\\ .PipelineName //" + annotations: + pipelinesascode.tekton.dev/target-namespace: "\\ .TargetNamespace //" + pipelinesascode.tekton.dev/on-target-branch: "[\\ .TargetBranch //]" + pipelinesascode.tekton.dev/on-event: "[\\ .TargetEvent //]" + pipelinesascode.tekton.dev/on-label: "[bug]" +spec: + pipelineSpec: + tasks: + - name: task + taskSpec: + steps: + - name: success + image: registry.access.redhat.com/ubi9/ubi-micro + script: | + echo "Labels on PR: " + for i in $(echo -e "{{ labels }}");do + echo $i + done