From b63beda751bb00c5cb877494bcdac326bc35345e Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Tue, 18 Feb 2025 10:33:25 +0100 Subject: [PATCH] feat: Report validation errors on pull requests This commit introduces the capability to report PipelineRun validation errors as comments on pull requests. This functionality is triggered when validation errors are detected and the event type is a pull request. The errors are formatted into a markdown table and posted as a comment on the pull request, allowing users to easily identify and address issues in their PipelineRun templates. The commit also includes updates to the provider interfaces to include a CreateComment function, and implementations for GitHub, GitLab, Bitbucket Cloud, Bitbucket Server and Gitea providers. Signed-off-by: Chmouel Boudjnah --- docs/content/docs/dev/_index.md | 4 +- docs/content/docs/guide/running.md | 35 +++++- .../report-error-comment-on-bad-yaml.png | Bin 0 -> 99247 bytes pkg/pipelineascode/match.go | 49 +++++++-- pkg/pipelineascode/match_test.go | 2 +- pkg/pipelineascode/pipelineascode_test.go | 38 ++++++- pkg/provider/bitbucketcloud/bitbucket.go | 4 + .../bitbucketserver/bitbucketserver.go | 4 + pkg/provider/gitea/gitea.go | 35 ++++++ pkg/provider/gitea/gitea_test.go | 98 +++++++++++++++++ pkg/provider/github/github.go | 41 +++++++ pkg/provider/github/github_test.go | 93 ++++++++++++++++ pkg/provider/gitlab/gitlab.go | 40 +++++++ pkg/provider/gitlab/gitlab_test.go | 104 ++++++++++++++++++ pkg/provider/interface.go | 1 + pkg/test/provider/testwebvcs.go | 4 + test/README.md | 5 + test/gitea_test.go | 51 ++++++++- test/github_pullrequest_test.go | 43 +++----- .../TestGithubSecondPullRequestBadYaml.golden | 6 + .../failures/pipeline-validation.yaml | 28 +++++ 21 files changed, 633 insertions(+), 52 deletions(-) create mode 100644 docs/static/images/report-error-comment-on-bad-yaml.png create mode 100644 test/testdata/TestGithubSecondPullRequestBadYaml.golden create mode 100644 test/testdata/failures/pipeline-validation.yaml diff --git a/docs/content/docs/dev/_index.md b/docs/content/docs/dev/_index.md index 05212f0f9..3f4200e21 100644 --- a/docs/content/docs/dev/_index.md +++ b/docs/content/docs/dev/_index.md @@ -179,12 +179,14 @@ For example, to test and lint the go files: make test lint-go ``` -If you add a CLI command with help, you will need to regenerate the golden files: +We use [golden](https://pkg.go.dev/gotest.tools/v3/golden) files in our tests, for instance, to compare the output of CLI commands or other detailed tests. Occasionally, you may need to regenerate the golden files if you modify the output of a command. For unit tests, you can use this Makefile target: ```shell make update-golden ``` +Head over to the [./test/README.md](./test/README.md) for more information on how to update the golden files on the E2E tests. + ## Configuring the Pre Push Git checks We are using several tools to verify that pipelines-as-code is up to a good diff --git a/docs/content/docs/guide/running.md b/docs/content/docs/guide/running.md index a42a04076..642200fcb 100644 --- a/docs/content/docs/guide/running.md +++ b/docs/content/docs/guide/running.md @@ -66,26 +66,26 @@ PipelineRun, another user who does have the necessary permissions can comment The `OWNERS` file follows a specific format similar to the Prow `OWNERS` file format (detailed at ). We support a basic `OWNERS` configuration with `approvers` and `reviewers` lists, -both of which have equal permissions for executing a `PipelineRun`. +both of which have equal permissions for executing a `PipelineRun`. If the `OWNERS` file uses `filters` instead of a simple configuration, we only consider the `.*` filter and extract the `approvers` and `reviewers` lists from -it. Any other filters targeting specific files or directories are ignored. +it. Any other filters targeting specific files or directories are ignored. Additionally, `OWNERS_ALIASES` is supported and allows mapping alias names to -lists of usernames. +lists of usernames. Including contributors in the `approvers` or `reviewers` lists within your `OWNERS` file grants them the ability to execute a `PipelineRun` via -Pipelines-as-Code. +Pipelines-as-Code. For example, if your repository’s `main` or `master` branch contains the -following `approvers` section: +following `approvers` section: ```yaml approvers: - approved -``` +``` The user with the username `"approved"` will have the necessary permissions. @@ -115,6 +115,29 @@ or on OpenShift using the OpenShift Console. Pipelines-as-Code will post a URL in the Checks tab for GitHub apps to let you click on it and follow the pipeline execution directly there. +## Errors When Parsing PipelineRun YAML + +When Pipelines-As-Code encounters an issue with the YAML formatting in the +repository, it will log the error in the user namespace events log and +the Pipelines-as-Code controller log. + +Despite the error, Pipelines-As-Code will continue to run other correctly parsed +and matched PipelineRuns. + +{{< support_matrix github_app="true" github_webhook="true" gitea="true" gitlab="true" bitbucket_cloud="false" bitbucket_server="false" >}} + +When an event is triggered from a Pull Request, a new comment will be created on +the Pull Request detailing the error. + +Subsequent iterations on the Pull Request will update the comment with any new +errors. + +If no new errors are detected, the comment will remain and will not be deleted. + +Here is an example of a YAML error being reported as a comment to a Pull Request: + +![report yaml error as comments](/images/report-error-comment-on-bad-yaml.png) + ## Cancelling ### Cancelling in-progress PipelineRuns diff --git a/docs/static/images/report-error-comment-on-bad-yaml.png b/docs/static/images/report-error-comment-on-bad-yaml.png new file mode 100644 index 0000000000000000000000000000000000000000..f8803ffea87ef5bddb758bcd36125428736d0b29 GIT binary patch literal 99247 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 `2KVIqs(%}>p{+M0 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 Mx~U1jeD~3R0dP%TbpQYW literal 0 HcmV?d00001 diff --git a/pkg/pipelineascode/match.go b/pkg/pipelineascode/match.go index c6171bd9a..5e6909bf2 100644 --- a/pkg/pipelineascode/match.go +++ b/pkg/pipelineascode/match.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "regexp" "strings" apipac "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys" @@ -20,6 +21,12 @@ import ( "go.uber.org/zap" ) +const validationErrorTemplate = `> [!CAUTION] +> There are some errors in your PipelineRun template. + +| PipelineRun | Error | +|------|-------|` + func (p *PacRun) matchRepoPR(ctx context.Context) ([]matcher.Match, *v1alpha1.Repository, error) { repo, err := p.verifyRepoAndUser(ctx) if err != nil { @@ -172,10 +179,16 @@ func (p *PacRun) getPipelineRunsFromRepo(ctx context.Context, repo *v1alpha1.Rep rawTemplates, err := p.vcx.GetTektonDir(ctx, p.event, tektonDir, provenance) if err != nil && strings.Contains(err.Error(), "error unmarshalling yaml file") { // make the error a bit more friendly for users who don't know what marshalling or intricacies of the yaml parser works - errmsg := err.Error() - errmsg = strings.ReplaceAll(errmsg, " error converting YAML to JSON: yaml:", "") - errmsg = strings.ReplaceAll(errmsg, "unmarshalling", "while parsing the") - return nil, fmt.Errorf("%s", errmsg) + // format is "error unmarshalling yaml file pr-bad-format.yaml: yaml: line 3: could not find expected ':'" + // get the filename with a regexp + reg := regexp.MustCompile(`error unmarshalling yaml file\s([^:]*):\s*(yaml:\s*)?(.*)`) + matches := reg.FindStringSubmatch(err.Error()) + if len(matches) == 4 { + p.reportValidationErrors(ctx, repo, map[string]string{matches[1]: matches[3]}) + return nil, nil + } + + return nil, err } if err != nil || rawTemplates == "" { msg := fmt.Sprintf("cannot locate templates in %s/ directory for this repository in %s", tektonDir, p.event.HeadBranch) @@ -225,15 +238,12 @@ func (p *PacRun) getPipelineRunsFromRepo(ctx context.Context, repo *v1alpha1.Rep return nil, err } - if types.ValidationErrors != nil { - for k, v := range types.ValidationErrors { - kv := fmt.Sprintf("prun: %s tekton validation error: %s", k, v) - p.eventEmitter.EmitMessage(repo, zap.ErrorLevel, "PipelineRunValidationErrors", kv) - } + if len(types.ValidationErrors) > 0 && p.event.TriggerTarget == triggertype.PullRequest { + p.reportValidationErrors(ctx, repo, types.ValidationErrors) } pipelineRuns := types.PipelineRuns if len(pipelineRuns) == 0 { - msg := fmt.Sprintf("cannot locate templates in %s/ directory for this repository in %s", tektonDir, p.event.HeadBranch) + msg := fmt.Sprintf("cannot locate valid templates in %s/ directory for this repository in %s", tektonDir, p.event.HeadBranch) p.eventEmitter.EmitMessage(nil, zap.InfoLevel, "RepositoryCannotLocatePipelineRun", msg) return nil, nil } @@ -430,3 +440,22 @@ func (p *PacRun) createNeutralStatus(ctx context.Context) error { return nil } + +// reportValidationErrors reports validation errors found in PipelineRuns by: +// 1. Creating error messages for each validation error +// 2. Emitting error messages to the event system +// 3. Creating a markdown formatted comment on the repository with all errors. +func (p *PacRun) reportValidationErrors(ctx context.Context, repo *v1alpha1.Repository, validationErrors map[string]string) { + errorRows := make([]string, 0, len(validationErrors)) + for name, err := range validationErrors { + errorRows = append(errorRows, fmt.Sprintf("| %s | `%s` |", name, err)) + p.eventEmitter.EmitMessage(repo, zap.ErrorLevel, "PipelineRunValidationErrors", + fmt.Sprintf("cannot read the PipelineRun: %s, error: %s", name, err)) + } + markdownErrMessage := fmt.Sprintf(`%s +%s`, validationErrorTemplate, strings.Join(errorRows, "\n")) + if err := p.vcx.CreateComment(ctx, p.event, markdownErrMessage, validationErrorTemplate); err != nil { + p.eventEmitter.EmitMessage(repo, zap.ErrorLevel, "PipelineRunValidationErrors", + fmt.Sprintf("failed to create comment: %s", err.Error())) + } +} diff --git a/pkg/pipelineascode/match_test.go b/pkg/pipelineascode/match_test.go index e01fe885f..e76619331 100644 --- a/pkg/pipelineascode/match_test.go +++ b/pkg/pipelineascode/match_test.go @@ -167,7 +167,7 @@ func TestGetPipelineRunsFromRepo(t *testing.T) { }, tektondir: "testdata/invalid_tekton_yaml", event: pullRequestEvent, - logSnippet: `prun: bad-tekton-yaml tekton validation error: json: cannot unmarshal object into Go struct field PipelineSpec.spec.pipelineSpec.tasks of type []v1beta1.PipelineTask`, + logSnippet: `json: cannot unmarshal object into Go struct field PipelineSpec.spec.pipelineSpec.tasks of type []v1beta1.PipelineTask`, }, { name: "no-match pipelineruns in .tekton dir, only matched should be returned", diff --git a/pkg/pipelineascode/pipelineascode_test.go b/pkg/pipelineascode/pipelineascode_test.go index bead914ea..0e30532c9 100644 --- a/pkg/pipelineascode/pipelineascode_test.go +++ b/pkg/pipelineascode/pipelineascode_test.go @@ -9,6 +9,7 @@ import ( "io" "net/http" "path/filepath" + "regexp" "strings" "sync" "testing" @@ -63,11 +64,6 @@ func testSetupCommonGhReplies(t *testing.T, mux *http.ServeMux, runevent info.Ev fmt.Sprintf("/repos/%s/%s/statuses/%s", runevent.Organization, runevent.Repository, runevent.SHA), "{}") - // using 666 as pull request number - replyString(mux, - fmt.Sprintf("/repos/%s/%s/issues/666/comments", runevent.Organization, runevent.Repository), - "{}") - jj := fmt.Sprintf(`{"sha": "%s", "html_url": "https://git.commit.url/%s", "message": "commit message"}`, runevent.SHA, runevent.SHA) replyString(mux, @@ -131,6 +127,7 @@ func TestRun(t *testing.T) { PayloadEncodedSecret string concurrencyLimit int expectedLogSnippet string + expectedPostedComment string // TODO: multiple posted comments when we need it }{ { name: "pull request/fail-to-start-apps", @@ -149,6 +146,23 @@ func TestRun(t *testing.T) { finalStatus: "failure", finalStatusText: "we need at least one pipelinerun to start with", }, + { + name: "pull request/bad-yaml", + runevent: info.Event{ + SHA: "principale", + Organization: "owner", + Repository: "lagaffe", + URL: "https://service/documentation", + HeadBranch: "press", + BaseBranch: "main", + Sender: "owner", + EventType: "pull_request", + TriggerTarget: "pull_request", + PullRequestNumber: 666, + }, + tektondir: "testdata/bad_yaml", + expectedPostedComment: ".*There are some errors in your PipelineRun template.*line 2: did not find expected key", + }, { name: "pull request/unknown-remotetask-but-fail-on-matching", runevent: info.Event{ @@ -548,6 +562,20 @@ func TestRun(t *testing.T) { ghtesthelper.SetupGitTree(t, mux, tt.tektondir, &tt.runevent, false) } + // using 666 as pull request number + mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", tt.runevent.Organization, tt.runevent.Repository, tt.runevent.PullRequestNumber), + func(w http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodPost { + _, _ = fmt.Fprintf(w, `{"id": %d}`, tt.runevent.PullRequestNumber) + // read body and compare it + body, _ := io.ReadAll(req.Body) + expectedRegexp := regexp.MustCompile(tt.expectedPostedComment) + assert.Assert(t, expectedRegexp.Match(body), "expected comment %s, got %s", tt.expectedPostedComment, string(body)) + return + } + _, _ = fmt.Fprint(w, `[]`) + }) + stdata, _ := testclient.SeedTestData(t, ctx, tdata) cs := ¶ms.Run{ Clients: clients.Clients{ diff --git a/pkg/provider/bitbucketcloud/bitbucket.go b/pkg/provider/bitbucketcloud/bitbucket.go index 48bd9a67b..3dc613a5f 100644 --- a/pkg/provider/bitbucketcloud/bitbucket.go +++ b/pkg/provider/bitbucketcloud/bitbucket.go @@ -31,6 +31,10 @@ type Provider struct { provenance string } +func (v *Provider) CreateComment(_ context.Context, _ *info.Event, _, _ string) error { + return nil +} + // CheckPolicyAllowing TODO: Implement ME. func (v *Provider) CheckPolicyAllowing(_ context.Context, _ *info.Event, _ []string) (bool, string) { return false, "" diff --git a/pkg/provider/bitbucketserver/bitbucketserver.go b/pkg/provider/bitbucketserver/bitbucketserver.go index 9ca671fb2..f2d563bf2 100644 --- a/pkg/provider/bitbucketserver/bitbucketserver.go +++ b/pkg/provider/bitbucketserver/bitbucketserver.go @@ -45,6 +45,10 @@ type Provider struct { projectKey string } +func (v *Provider) CreateComment(_ context.Context, _ *info.Event, _, _ string) error { + return nil +} + func (v *Provider) SetPacInfo(pacInfo *info.PacOpts) { v.pacInfo = pacInfo } diff --git a/pkg/provider/gitea/gitea.go b/pkg/provider/gitea/gitea.go index 5328bf121..a06f4cccf 100644 --- a/pkg/provider/gitea/gitea.go +++ b/pkg/provider/gitea/gitea.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "path" + "regexp" "strconv" "strings" @@ -55,6 +56,40 @@ type Provider struct { run *params.Run } +func (v *Provider) CreateComment(_ context.Context, event *info.Event, commit, updateMarker string) error { + if v.Client == nil { + return fmt.Errorf("no gitea client has been initialized") + } + + if event.PullRequestNumber == 0 { + return fmt.Errorf("create comment only works on pull requests") + } + + // List comments of the PR + if updateMarker != "" { + comments, _, err := v.Client.ListIssueComments(event.Organization, event.Repository, int64(event.PullRequestNumber), gitea.ListIssueCommentOptions{}) + if err != nil { + return err + } + + for _, comment := range comments { + re := regexp.MustCompile(updateMarker) + if re.MatchString(comment.Body) { + _, _, err := v.Client.EditIssueComment(event.Organization, event.Repository, comment.ID, gitea.EditIssueCommentOption{ + Body: commit, + }) + return err + } + } + } + + _, _, err := v.Client.CreateIssueComment(event.Organization, event.Repository, int64(event.PullRequestNumber), gitea.CreateIssueCommentOption{ + Body: commit, + }) + + return err +} + func (v *Provider) SetPacInfo(pacInfo *info.PacOpts) { v.pacInfo = pacInfo } diff --git a/pkg/provider/gitea/gitea_test.go b/pkg/provider/gitea/gitea_test.go index 5e0a934d0..1db112366 100644 --- a/pkg/provider/gitea/gitea_test.go +++ b/pkg/provider/gitea/gitea_test.go @@ -525,3 +525,101 @@ func TestGetTektonDir(t *testing.T) { }) } } + +func TestCreateComment(t *testing.T) { + tests := []struct { + name string + event *info.Event + commit string + updateMarker string + mockResponses map[string]func(rw http.ResponseWriter, _ *http.Request) + wantErr string + clientNil bool + }{ + { + name: "nil client error", + clientNil: true, + event: &info.Event{PullRequestNumber: 123}, + wantErr: "no gitea client has been initialized", + }, + { + name: "not a pull request error", + event: &info.Event{PullRequestNumber: 0}, + wantErr: "create comment only works on pull requests", + }, + { + name: "create new comment", + event: &info.Event{Organization: "org", Repository: "repo", PullRequestNumber: 123}, + commit: "New Comment", + updateMarker: "", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/repos/org/repo/issues/123/comments": func(rw http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, http.MethodPost) + fmt.Fprint(rw, `{}`) + }, + }, + }, + { + name: "update existing comment", + event: &info.Event{Organization: "org", Repository: "repo", PullRequestNumber: 123}, + commit: "Updated Comment", + updateMarker: "MARKER", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/repos/org/repo/issues/123/comments": func(rw http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + fmt.Fprint(rw, `[{"id": 555, "body": "MARKER"}]`) + return + } + }, + "/repos/org/repo/issues/comments/555": func(rw http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "PATCH") + rw.WriteHeader(http.StatusOK) + fmt.Fprint(rw, `{}`) + }, + }, + }, + { + name: "no matching comment creates new", + event: &info.Event{Organization: "org", Repository: "repo", PullRequestNumber: 123}, + commit: "New Comment", + updateMarker: "MARKER", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/repos/org/repo/issues/123/comments": func(rw http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + fmt.Fprint(rw, `[{"id": 555, "body": "NO_MATCH"}]`) + return + } + assert.Equal(t, r.Method, http.MethodPost) + rw.WriteHeader(http.StatusCreated) + fmt.Fprint(rw, `{}`) + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeclient, mux, teardown := tgitea.Setup(t) + defer teardown() + + if tt.clientNil { + p := &Provider{} + err := p.CreateComment(context.Background(), tt.event, tt.commit, tt.updateMarker) + assert.ErrorContains(t, err, tt.wantErr) + return + } + + for endpoint, handler := range tt.mockResponses { + mux.HandleFunc(endpoint, handler) + } + + p := &Provider{Client: fakeclient} + err := p.CreateComment(context.Background(), tt.event, tt.commit, tt.updateMarker) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.NilError(t, err) + } + }) + } +} diff --git a/pkg/provider/github/github.go b/pkg/provider/github/github.go index d7c91bb4e..c12ce0e07 100644 --- a/pkg/provider/github/github.go +++ b/pkg/provider/github/github.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "regexp" "strings" "sync" "time" @@ -602,3 +603,43 @@ func (v *Provider) isBranchContainsCommit(ctx context.Context, runevent *info.Ev } return fmt.Errorf("provided SHA %s is not the HEAD commit of the branch %s", runevent.SHA, branchName) } + +// CreateComment creates a comment on a Pull Request. +func (v *Provider) CreateComment(ctx context.Context, event *info.Event, commit, updateMarker string) error { + if v.Client == nil { + return fmt.Errorf("no github client has been initialized") + } + + if event.PullRequestNumber == 0 { + return fmt.Errorf("create comment only works on pull requests") + } + + // List last page of the comments of the PR + if updateMarker != "" { + comments, _, err := v.Client.Issues.ListComments(ctx, event.Organization, event.Repository, event.PullRequestNumber, &github.IssueListCommentsOptions{ + ListOptions: github.ListOptions{ + Page: 1, + PerPage: 100, + }, + }) + if err != nil { + return err + } + + for _, comment := range comments { + re := regexp.MustCompile(updateMarker) + if re.MatchString(comment.GetBody()) { + _, _, err := v.Client.Issues.EditComment(ctx, event.Organization, event.Repository, comment.GetID(), &github.IssueComment{ + Body: &commit, + }) + return err + } + } + } + + _, _, err := v.Client.Issues.CreateComment(ctx, event.Organization, event.Repository, event.PullRequestNumber, &github.IssueComment{ + Body: &commit, + }) + + return err +} diff --git a/pkg/provider/github/github_test.go b/pkg/provider/github/github_test.go index a299a7a71..34dba2774 100644 --- a/pkg/provider/github/github_test.go +++ b/pkg/provider/github/github_test.go @@ -1130,3 +1130,96 @@ func TestGetBranch(t *testing.T) { }) } } + +func TestCreateComment(t *testing.T) { + tests := []struct { + name string + event *info.Event + updateMarker string + mockResponses map[string]func(rw http.ResponseWriter, _ *http.Request) + wantErr string + clientNil bool + }{ + { + name: "nil client error", + clientNil: true, + event: &info.Event{PullRequestNumber: 123}, + wantErr: "no github client has been initialized", + }, + { + name: "not a pull request error", + event: &info.Event{PullRequestNumber: 0}, + wantErr: "create comment only works on pull requests", + }, + { + name: "create new comment", + event: &info.Event{Organization: "org", Repository: "repo", PullRequestNumber: 123}, + updateMarker: "", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/repos/org/repo/issues/123/comments": func(rw http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, http.MethodPost) + rw.WriteHeader(http.StatusCreated) + }, + }, + }, + { + name: "update existing comment", + event: &info.Event{Organization: "org", Repository: "repo", PullRequestNumber: 123}, + updateMarker: "MARKER", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/repos/org/repo/issues/123/comments": func(rw http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + fmt.Fprint(rw, `[{"id": 555, "body": "MARKER"}]`) + return + } + }, + "/repos/org/repo/issues/comments/555": func(rw http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, http.MethodPatch) + rw.WriteHeader(http.StatusOK) + }, + }, + }, + { + name: "no matching comment creates new", + event: &info.Event{Organization: "org", Repository: "repo", PullRequestNumber: 123}, + updateMarker: "MARKER", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/repos/org/repo/issues/123/comments": func(rw http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + fmt.Fprint(rw, `[{"id": 555, "body": "NO_MATCH"}]`) + return + } + assert.Equal(t, r.Method, http.MethodPost) + rw.WriteHeader(http.StatusCreated) + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, _ := rtesting.SetupFakeContext(t) + + var provider *Provider + if !tt.clientNil { + fakeclient, mux, _, teardown := ghtesthelper.SetupGH() + defer teardown() + provider = &Provider{Client: fakeclient} + + for pattern, handler := range tt.mockResponses { + mux.HandleFunc(pattern, handler) + } + } else { + provider = &Provider{} // nil client + } + + err := provider.CreateComment(ctx, tt.event, "comment body", tt.updateMarker) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + assert.NilError(t, err) + }) + } +} diff --git a/pkg/provider/gitlab/gitlab.go b/pkg/provider/gitlab/gitlab.go index 6d55579a1..acf943a30 100644 --- a/pkg/provider/gitlab/gitlab.go +++ b/pkg/provider/gitlab/gitlab.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "path/filepath" + "regexp" "strings" "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1" @@ -64,6 +65,45 @@ func (v *Provider) SetPacInfo(pacInfo *info.PacOpts) { v.pacInfo = pacInfo } +func (v *Provider) CreateComment(_ context.Context, event *info.Event, commit, updateMarker string) error { + if v.Client == nil { + return fmt.Errorf("no gitlab client has been initialized") + } + + if event.PullRequestNumber == 0 { + return fmt.Errorf("create comment only works on merge requests") + } + + // List comments of the merge request + if updateMarker != "" { + comments, _, err := v.Client.Notes.ListMergeRequestNotes(v.sourceProjectID, event.PullRequestNumber, &gitlab.ListMergeRequestNotesOptions{ + ListOptions: gitlab.ListOptions{ + Page: 1, + PerPage: 100, + }, + }) + if err != nil { + return err + } + + for _, comment := range comments { + re := regexp.MustCompile(updateMarker) + if re.MatchString(comment.Body) { + _, _, err := v.Client.Notes.UpdateMergeRequestNote(v.sourceProjectID, event.PullRequestNumber, comment.ID, &gitlab.UpdateMergeRequestNoteOptions{ + Body: &commit, + }) + return err + } + } + } + + _, _, err := v.Client.Notes.CreateMergeRequestNote(v.sourceProjectID, event.PullRequestNumber, &gitlab.CreateMergeRequestNoteOptions{ + Body: &commit, + }) + + return err +} + // CheckPolicyAllowing TODO: Implement ME. func (v *Provider) CheckPolicyAllowing(_ context.Context, _ *info.Event, _ []string) (bool, string) { return false, "" diff --git a/pkg/provider/gitlab/gitlab_test.go b/pkg/provider/gitlab/gitlab_test.go index 0522cce6d..075b7ada9 100644 --- a/pkg/provider/gitlab/gitlab_test.go +++ b/pkg/provider/gitlab/gitlab_test.go @@ -705,3 +705,107 @@ func TestGetFiles(t *testing.T) { }) } } + +func TestGitLabCreateComment(t *testing.T) { + tests := []struct { + name string + event *info.Event + commit string + updateMarker string + mockResponses map[string]func(rw http.ResponseWriter, _ *http.Request) + wantErr string + clientNil bool + }{ + { + name: "nil client error", + clientNil: true, + event: &info.Event{PullRequestNumber: 123}, + wantErr: "no gitlab client has been initialized", + }, + { + name: "not a merge request error", + event: &info.Event{PullRequestNumber: 0}, + wantErr: "create comment only works on merge requests", + }, + { + name: "create new comment", + event: &info.Event{PullRequestNumber: 123}, + commit: "New Comment", + updateMarker: "", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/projects/666/merge_requests/123/notes": func(rw http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, http.MethodPost) + rw.WriteHeader(http.StatusCreated) + fmt.Fprint(rw, `{}`) + }, + }, + }, + { + name: "update existing comment", + event: &info.Event{PullRequestNumber: 123}, + commit: "Updated Comment", + updateMarker: "MARKER", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/projects/666/merge_requests/123/notes": func(rw http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + fmt.Fprint(rw, `[{"id": 555, "body": "MARKER"}]`) + return + } + }, + "/projects/666/merge_requests/123/notes/555": func(rw http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "PUT") + rw.WriteHeader(http.StatusOK) + fmt.Fprint(rw, `{}`) + }, + }, + }, + { + name: "no matching comment creates new", + event: &info.Event{PullRequestNumber: 123}, + commit: "New Comment", + updateMarker: "MARKER", + mockResponses: map[string]func(rw http.ResponseWriter, _ *http.Request){ + "/projects/666/merge_requests/123/notes": func(rw http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + fmt.Fprint(rw, `[{"id": 555, "body": "NO_MATCH"}]`) + return + } + assert.Equal(t, r.Method, http.MethodPost) + rw.WriteHeader(http.StatusCreated) + fmt.Fprint(rw, `{}`) + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeclient, mux, teardown := thelp.Setup(t) + defer teardown() + + if tt.clientNil { + p := &Provider{ + sourceProjectID: 666, + } + err := p.CreateComment(context.Background(), tt.event, tt.commit, tt.updateMarker) + assert.ErrorContains(t, err, tt.wantErr) + return + } + + for endpoint, handler := range tt.mockResponses { + mux.HandleFunc(endpoint, handler) + } + + p := &Provider{ + sourceProjectID: 666, + Client: fakeclient, + } + err := p.CreateComment(context.Background(), tt.event, tt.commit, tt.updateMarker) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.NilError(t, err) + } + }) + } +} diff --git a/pkg/provider/interface.go b/pkg/provider/interface.go index 528081f57..fd7c0287d 100644 --- a/pkg/provider/interface.go +++ b/pkg/provider/interface.go @@ -44,6 +44,7 @@ type Interface interface { GetTaskURI(ctx context.Context, event *info.Event, uri string) (bool, string, error) CreateToken(context.Context, []string, *info.Event) (string, error) CheckPolicyAllowing(context.Context, *info.Event, []string) (bool, string) + CreateComment(ctx context.Context, event *info.Event, comment, updateMarker string) error } const DefaultProviderAPIUser = "git" diff --git a/pkg/test/provider/testwebvcs.go b/pkg/test/provider/testwebvcs.go index fbb75e633..cf2eec1d5 100644 --- a/pkg/test/provider/testwebvcs.go +++ b/pkg/test/provider/testwebvcs.go @@ -48,6 +48,10 @@ func (v *TestProviderImp) IsAllowedOwnersFile(_ context.Context, _ *info.Event) return v.AllowedInOwnersFile, nil } +func (v *TestProviderImp) CreateComment(_ context.Context, _ *info.Event, _, _ string) error { + return nil +} + func (v *TestProviderImp) SetLogger(_ *zap.SugaredLogger) { } diff --git a/test/README.md b/test/README.md index 3d2143708..c87713e0a 100644 --- a/test/README.md +++ b/test/README.md @@ -76,6 +76,11 @@ You can specify only a subsets of test to run with : same goes for `TestGitlab` or other methods. +If you need to update the golden files in the e2e test you add the flag +`-test.update-golden=true` to the go test command line to update the golden +files. Run it at first if the test output is supposed to change (or on a new +test) and then run without to make sure it comply. + ## Running nightly tests Some tests are set as nightly which mean not run on every PR, because exposing rate limitation often. diff --git a/test/gitea_test.go b/test/gitea_test.go index d245e6e1b..7b35315d3 100644 --- a/test/gitea_test.go +++ b/test/gitea_test.go @@ -173,13 +173,57 @@ func TestGiteaStepActions(t *testing.T) { tgitea.WaitForSecretDeletion(t, topts, topts.TargetRefName) } +// TestGiteaBadYamlReportingOnPR makes sure that we can catch a bad yaml file +// and report on PR, we only do updates and not creating a new comment all the +// time. +func TestGiteaBadYamlReportingOnPR(t *testing.T) { + topts := &tgitea.TestOpts{ + TargetEvent: triggertype.PullRequest.String(), + YAMLFiles: map[string]string{".tekton/pr-bad-validation.yaml": "testdata/failures/pipeline-validation.yaml"}, + ExpectEvents: true, + } + + _, f := tgitea.TestPR(t, topts) + defer f() + topts.Regexp = regexp.MustCompile(`.*bad-valid | .json: cannot unmarshal array into Go struct field PipelineRunSpec.spec.pipelineSpec of type v1.PipelineSpec.*`) + tgitea.WaitForPullRequestCommentMatch(t, topts) + + comments, _, err := topts.GiteaCNX.Client.ListRepoIssueComments(topts.PullRequest.Base.Repository.Owner.UserName, topts.PullRequest.Base.Repository.Name, gitea.ListIssueCommentOptions{}) + assert.NilError(t, err) + assert.Equal(t, len(comments), 1, "should have only one comment") + + // sending a second time the comment should have been updated + scmOpts := &scm.Opts{ + GitURL: topts.GitCloneURL, + Log: topts.ParamsRun.Clients.Log, + WebURL: topts.GitHTMLURL, + TargetRefName: topts.TargetRefName, + BaseRefName: topts.DefaultBranch, + PushForce: true, + } + processed, err := payload.ApplyTemplate("testdata/failures/pipeline-validation.yaml", map[string]string{ + "TargetNamespace": topts.TargetNS, + "TargetBranch": topts.DefaultBranch, + "TargetEvent": topts.TargetEvent, + "PipelineName": "pr-a-second-time", + "Command": "sleep 10", + }) + assert.NilError(t, err) + entries := map[string]string{".tekton/pr-bad-validation.yaml": processed} + _ = scm.PushFilesToRefGit(t, scmOpts, entries) + + comments, _, err = topts.GiteaCNX.Client.ListRepoIssueComments(topts.PullRequest.Base.Repository.Owner.UserName, topts.PullRequest.Base.Repository.Name, gitea.ListIssueCommentOptions{}) + assert.NilError(t, err) + assert.Equal(t, len(comments), 1, "should have only one comment") +} + // TestGiteaBadYaml we can't check pr status but this shows up in the // controller, so let's dig ourself in there.... TargetNS is a random string, so // it can only success if it matches it. -func TestGiteaBadYaml(t *testing.T) { +func TestGiteaBadYamlValidation(t *testing.T) { topts := &tgitea.TestOpts{ TargetEvent: triggertype.PullRequest.String(), - YAMLFiles: map[string]string{".tekton/pr-bad-format.yaml": "testdata/failures/pipeline_bad_format.yaml"}, + YAMLFiles: map[string]string{".tekton/pr-bad-format.yaml": "testdata/failures/bad-yaml.yaml"}, ExpectEvents: true, } @@ -187,7 +231,8 @@ func TestGiteaBadYaml(t *testing.T) { defer f() maxLines := int64(20) assert.NilError(t, twait.RegexpMatchingInControllerLog(ctx, topts.ParamsRun, *regexp.MustCompile( - "pipelinerun.*has failed.*expected exactly one, got neither: spec.pipelineRef, spec.pipelineSpec"), 10, "controller", &maxLines)) + "cannot read the PipelineRun: pr-bad-format.yaml, error: line 3: could not find expected ':'"), + 10, "controller", &maxLines)) } // TestGiteaInvalidSpecValues tests invalid field values of a PipelinRun and ensures that these diff --git a/test/github_pullrequest_test.go b/test/github_pullrequest_test.go index ab4a7a7bc..803c6ac4d 100644 --- a/test/github_pullrequest_test.go +++ b/test/github_pullrequest_test.go @@ -160,10 +160,10 @@ func TestGithubPullRequestWebhook(t *testing.T) { defer g.TearDown(ctx, t) } -func TestGithubPullRequestSecondBadYaml(t *testing.T) { +func TestGithubSecondPullRequestBadYaml(t *testing.T) { ctx := context.Background() g := &tgithub.PRTest{ - Label: "Github Rerequest", + Label: "Github PullRequest Bad Yaml", YamlFiles: []string{"testdata/failures/bad-yaml.yaml"}, SecondController: true, NoStatusCheck: true, @@ -171,33 +171,24 @@ func TestGithubPullRequestSecondBadYaml(t *testing.T) { g.RunPullRequest(ctx, t) defer g.TearDown(ctx, t) - opt := github.ListOptions{} - res := &github.ListCheckRunsResults{} - resp := &github.Response{} - var err error - 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, - }) + maxLoop := 10 + for i := 0; i < maxLoop; i++ { + comments, _, err := g.Provider.Client.Issues.ListComments( + ctx, g.Options.Organization, g.Options.Repo, g.PRNumber, + &github.IssueListCommentsOptions{}) 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 + + if len(comments) > 0 { + assert.Assert(t, len(comments) == 1, "Should have only one comment created we got way too many: %+v", comments) + golden.Assert(t, comments[0].GetBody(), strings.ReplaceAll(fmt.Sprintf("%s.golden", t.Name()), "/", "-")) + return } - time.Sleep(5 * time.Second) + + g.Cnx.Clients.Log.Infof("Looping %d/%d waiting for a comment to appear", i, maxLoop) + time.Sleep(6 * time.Second) } - assert.Equal(t, len(res.CheckRuns), 1) - assert.Equal(t, res.CheckRuns[0].GetOutput().GetTitle(), "pipelinerun start failure") - // may be fragile if we change the application name, but life goes on if it fails and we fix the name if that happen - assert.Equal(t, res.CheckRuns[0].GetOutput().GetSummary(), "Pipelines as Code GHE has failed.") - golden.Assert(t, res.CheckRuns[0].GetOutput().GetText(), strings.ReplaceAll(fmt.Sprintf("%s.golden", t.Name()), "/", "-")) + + t.Fatal("No comments with the pipelinerun error found on the pull request") } // TestGithubPullRequestInvalidSpecValues tests invalid field values of a PipelinRun and diff --git a/test/testdata/TestGithubSecondPullRequestBadYaml.golden b/test/testdata/TestGithubSecondPullRequestBadYaml.golden new file mode 100644 index 000000000..946349c16 --- /dev/null +++ b/test/testdata/TestGithubSecondPullRequestBadYaml.golden @@ -0,0 +1,6 @@ +> [!CAUTION] +> There are some errors in your PipelineRun template. + +| PipelineRun | Error | +|------|-------| +| bad-yaml.yaml | `line 3: could not find expected ':'` | \ No newline at end of file diff --git a/test/testdata/failures/pipeline-validation.yaml b/test/testdata/failures/pipeline-validation.yaml new file mode 100644 index 000000000..07365b145 --- /dev/null +++ b/test/testdata/failures/pipeline-validation.yaml @@ -0,0 +1,28 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + name: bad-valid + annotations: + pipelinesascode.tekton.dev/on-event: "[pull_request]" + pipelinesascode.tekton.dev/on-target-branch: "[main]" + pipelinesascode.tekton.dev/max-keep-runs: "5" +spec: + pipelineSpec: + # Customize this task if you like, or just do a taskRef + # to one of the hub task. + - name: noop-task + displayName: Task with no effect + taskSpec: + steps: + - name: noop-task + image: registry.access.redhat.com/ubi9/ubi-micro + script: | + exit 0 + - name: noop-task-2 + displayName: Task with no effect + taskSpec: + steps: + - name: noop-task + image: registry.access.redhat.com/ubi9/ubi-micro + script: | + exit 1