Skip to content

Commit dd58833

Browse files
zakiskchmouel
authored andcommitted
fix(github): resolve pull_request_number on retest for pushed commits
- The {{ pull_request_number }} variable was not substituted when a /retest command was issued on a pushed commit (e.g. a commit resulting from a PR merge) because the commit comment handler in ParsePayload did not fetch the associated pull requests for the commit SHA. - Fetch PRs via getPullRequestsWithCommit in handleCommitCommentEvent so the PullRequestNumber is set on the event before variable substitution runs. - Add a unit test for commit_comment events to verify the PullRequestNumber is populated from the associated PR. - Add an e2e test that merges a PR, issues /retest on the merged commit, and asserts the PipelineRun logs contain the correct pull request number. - Add a new testdata PipelineRun fixture that echoes the {{ pull_request_number }} variable for validation. https://issues.redhat.com/browse/SRVKP-10662 Signed-off-by: Zaki Shaikh <zashaikh@redhat.com>
1 parent 2ec9bb3 commit dd58833

File tree

5 files changed

+233
-6
lines changed

5 files changed

+233
-6
lines changed

pkg/provider/github/github.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type Provider struct {
6161
skippedRun
6262
triggerEvent string
6363
cachedChangedFiles *changedfiles.ChangedFiles
64+
commitInfo *github.Commit
6465
}
6566

6667
type skippedRun struct {
@@ -401,11 +402,17 @@ func (v *Provider) GetCommitInfo(ctx context.Context, runevent *info.Event) erro
401402
sha = branchinfo.Commit.GetSHA()
402403
}
403404
var err error
404-
commit, _, err = wrapAPI(v, "get_commit", func() (*github.Commit, *github.Response, error) {
405-
return v.Client().Git.GetCommit(ctx, runevent.Organization, runevent.Repository, sha)
406-
})
407-
if err != nil {
408-
return err
405+
406+
// check if the commit info is already cached in provider
407+
if v.commitInfo == nil {
408+
commit, _, err = wrapAPI(v, "get_commit", func() (*github.Commit, *github.Response, error) {
409+
return v.Client().Git.GetCommit(ctx, runevent.Organization, runevent.Repository, sha)
410+
})
411+
if err != nil {
412+
return err
413+
}
414+
} else {
415+
commit = v.commitInfo
409416
}
410417

411418
runevent.SHAURL = commit.GetHTMLURL()

pkg/provider/github/parse_payload.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,15 +622,37 @@ func (v *Provider) handleCommitCommentEvent(ctx context.Context, event *github.C
622622
runevent.BaseURL = runevent.HeadURL
623623
runevent.TriggerTarget = triggertype.Push
624624
opscomments.SetEventTypeAndTargetPR(runevent, event.GetComment().GetBody())
625+
v.userType = event.GetSender().GetType()
625626

626627
defaultBranch := event.GetRepo().GetDefaultBranch()
627628
// Set Event.Repository.DefaultBranch as default branch to runevent.HeadBranch, runevent.BaseBranch
629+
630+
commit, _, err := v.Client().Git.GetCommit(ctx, runevent.Organization, runevent.Repository, runevent.SHA)
631+
if err != nil {
632+
return runevent, fmt.Errorf("error getting commit %s: %w", runevent.SHA, err)
633+
}
634+
635+
// as we're going to make GetCommit API again in GetCommitInfo func, it will be cached in provider
636+
// so that we're wasting one API call
637+
v.commitInfo = commit
638+
639+
// when the commit is a merge commit, either email is 'noreply@github.com' or name is 'web-flow'
640+
isMergeCommit := commit.GetCommitter().GetEmail() == githubNoreplyEmail ||
641+
commit.GetCommitter().GetName() == githubWebFlowUser
642+
643+
prs, err := v.getPullRequestsWithCommit(ctx, runevent.SHA, runevent.Organization, runevent.Repository, isMergeCommit)
644+
if err != nil {
645+
v.Logger.Warnf("Error getting pull requests associated with the commit in this commit comment event: %v", err)
646+
}
647+
if len(prs) > 0 {
648+
runevent.PullRequestNumber = prs[0].GetNumber()
649+
}
650+
628651
runevent.HeadBranch, runevent.BaseBranch = defaultBranch, defaultBranch
629652
var (
630653
branchName string
631654
prName string
632655
tagName string
633-
err error
634656
)
635657

636658
// If it is a /test or /retest comment with pipelinerun name figure out the pipelinerun name

pkg/provider/github/parse_payload_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,9 @@ func TestParsePayLoad(t *testing.T) {
407407
targetCancelPipelinerun string
408408
wantedBranchName string
409409
wantedTagName string
410+
wantedPullRequestNumber int
410411
isCancelPipelineRunEnabled bool
412+
isMergeCommit bool
411413
skipPushEventForPRCommits bool
412414
objectType string
413415
}{
@@ -936,6 +938,28 @@ func TestParsePayLoad(t *testing.T) {
936938
wantedBranchName: "test1",
937939
isCancelPipelineRunEnabled: true,
938940
},
941+
{
942+
name: "good/commit comment want pull request number",
943+
eventType: "commit_comment",
944+
triggerTarget: "push",
945+
githubClient: true,
946+
payloadEventStruct: github.CommitCommentEvent{
947+
Repo: sampleRepo,
948+
Comment: &github.RepositoryComment{
949+
CommitID: github.Ptr("samplePRsha"),
950+
HTMLURL: github.Ptr("/888"),
951+
Body: github.Ptr("/retest dummy"),
952+
},
953+
},
954+
muxReplies: map[string]any{
955+
"/repos/owner/reponame/pulls/8881": samplePR,
956+
"/repos/owner/reponame/commits/samplePRsha/pulls": []*github.PullRequest{&samplePR},
957+
},
958+
shaRet: "samplePRsha",
959+
targetPipelinerun: "dummy",
960+
wantedBranchName: "main",
961+
wantedPullRequestNumber: 54321,
962+
},
939963
{
940964
name: "bad/commit comment for cancel a pr with invalid branch name",
941965
eventType: "commit_comment",
@@ -975,6 +999,25 @@ func TestParsePayLoad(t *testing.T) {
975999
wantedBranchName: "main",
9761000
wantErrString: "provided SHA samplePRshanew is not the HEAD commit of the branch main",
9771001
},
1002+
{
1003+
name: "commit comment to retest a pr with a merge commit",
1004+
eventType: "commit_comment",
1005+
triggerTarget: "push",
1006+
githubClient: true,
1007+
payloadEventStruct: github.CommitCommentEvent{
1008+
Repo: sampleRepo,
1009+
Comment: &github.RepositoryComment{
1010+
CommitID: github.Ptr("samplePRsha"),
1011+
HTMLURL: github.Ptr("/777"),
1012+
Body: github.Ptr("/retest dummy"),
1013+
},
1014+
},
1015+
muxReplies: map[string]any{"/repos/owner/reponame/pulls/777": samplePR},
1016+
shaRet: "samplePRsha",
1017+
targetPipelinerun: "dummy",
1018+
wantedBranchName: "main",
1019+
isMergeCommit: true,
1020+
},
9781021
{
9791022
name: "good/skip push event for skip-pr-commits setting",
9801023
eventType: "push",
@@ -1076,6 +1119,24 @@ func TestParsePayLoad(t *testing.T) {
10761119
bjeez, _ := json.Marshal(tag)
10771120
fmt.Fprint(rw, string(bjeez))
10781121
})
1122+
1123+
mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/git/commits/%s", "owner", "reponame", tt.shaRet), func(rw http.ResponseWriter, _ *http.Request) {
1124+
name := "test"
1125+
email := "test@test.com"
1126+
if tt.isMergeCommit {
1127+
name = "web-flow"
1128+
email = "noreply@github.com"
1129+
}
1130+
commit := &github.Commit{
1131+
SHA: github.Ptr(tt.shaRet),
1132+
Committer: &github.CommitAuthor{
1133+
Email: github.Ptr(email),
1134+
Name: github.Ptr(name),
1135+
},
1136+
}
1137+
bjeez, _ := json.Marshal(commit)
1138+
fmt.Fprint(rw, string(bjeez))
1139+
})
10791140
}
10801141

10811142
logger, _ := logger.GetLogger()
@@ -1117,6 +1178,9 @@ func TestParsePayLoad(t *testing.T) {
11171178
assert.Equal(t, tt.wantedBranchName, ret.BaseBranch)
11181179
assert.Equal(t, tt.isCancelPipelineRunEnabled, ret.CancelPipelineRuns)
11191180
}
1181+
if tt.wantedPullRequestNumber != 0 {
1182+
assert.Equal(t, tt.wantedPullRequestNumber, ret.PullRequestNumber)
1183+
}
11201184
if tt.targetPipelinerun != "" {
11211185
assert.Equal(t, tt.targetPipelinerun, ret.TargetTestPipelineRun)
11221186
}

test/github_push_retest_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ package test
55
import (
66
"context"
77
"fmt"
8+
"net/http"
9+
"os"
810
"regexp"
911
"testing"
1012
"time"
1113

1214
"github.com/google/go-github/v81/github"
1315
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
16+
"github.com/openshift-pipelines/pipelines-as-code/pkg/opscomments"
17+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
1418
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/cctx"
1519
tgithub "github.com/openshift-pipelines/pipelines-as-code/test/pkg/github"
1620
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/options"
21+
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/payload"
1722
twait "github.com/openshift-pipelines/pipelines-as-code/test/pkg/wait"
1823
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
24+
"github.com/tektoncd/pipeline/pkg/names"
1925
"gotest.tools/v3/assert"
2026
corev1 "k8s.io/api/core/v1"
2127
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -217,3 +223,112 @@ func TestGithubSecondPushRequestGitOpsCommentCancel(t *testing.T) {
217223
}
218224
assert.Assert(t, cancelled, "No cancelled pipeline run found")
219225
}
226+
227+
func TestGithubPullRequestRetestPullRequestNumberSubstitution(t *testing.T) {
228+
targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns")
229+
ctx := context.Background()
230+
g := &tgithub.PRTest{}
231+
232+
ctx, runcnx, opts, ghcnx, err := tgithub.Setup(ctx, g.SecondController, g.Webhook)
233+
assert.NilError(t, err)
234+
g.Logger = runcnx.Clients.Log
235+
236+
g.CommitTitle = fmt.Sprintf("Testing %s with Github APPS integration on %s", g.Label, targetNS)
237+
g.Logger.Info(g.CommitTitle)
238+
239+
repoinfo, resp, err := ghcnx.Client().Repositories.Get(ctx, opts.Organization, opts.Repo)
240+
assert.NilError(t, err)
241+
if resp != nil && resp.StatusCode == http.StatusNotFound {
242+
t.Errorf("Repository %s not found in %s", opts.Organization, opts.Repo)
243+
}
244+
245+
if g.Options.Settings.Github != nil {
246+
opts.Settings = g.Options.Settings
247+
}
248+
err = tgithub.CreateCRD(ctx, t, repoinfo, runcnx, opts, targetNS)
249+
assert.NilError(t, err)
250+
251+
tempBaseBranch := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("temp-base-branch")
252+
tempBaseBranchRef := fmt.Sprintf("refs/heads/%s", tempBaseBranch)
253+
254+
// Get the SHA of the default branch
255+
defaultBranchRef, _, err := ghcnx.Client().Git.GetRef(ctx, opts.Organization, opts.Repo, fmt.Sprintf("refs/heads/%s", repoinfo.GetDefaultBranch()))
256+
assert.NilError(t, err)
257+
258+
// Create the temporary base branch
259+
_, _, err = ghcnx.Client().Git.CreateRef(ctx, opts.Organization, opts.Repo, github.CreateRef{
260+
Ref: tempBaseBranchRef,
261+
SHA: *defaultBranchRef.Object.SHA,
262+
})
263+
assert.NilError(t, err)
264+
265+
yamlEntries := map[string]string{".tekton/pipelinerun-pr-number-variable.yaml": "testdata/pipelinerun-pr-number-variable.yaml"}
266+
267+
entries, err := payload.GetEntries(yamlEntries, targetNS, tempBaseBranchRef, triggertype.Push.String(), map[string]string{})
268+
assert.NilError(t, err)
269+
270+
targetRefName := fmt.Sprintf("refs/heads/%s",
271+
names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test"))
272+
273+
sha, vref, err := tgithub.PushFilesToRef(ctx, ghcnx.Client(), g.CommitTitle, tempBaseBranchRef, targetRefName,
274+
opts.Organization, opts.Repo, entries)
275+
assert.NilError(t, err)
276+
277+
g.Logger.Infof("Commit %s has been created and pushed to %s", sha, vref.GetURL())
278+
number, err := tgithub.PRCreate(ctx, runcnx, ghcnx, opts.Organization,
279+
opts.Repo, targetRefName, tempBaseBranchRef, g.CommitTitle)
280+
assert.NilError(t, err)
281+
282+
g.Cnx = runcnx
283+
g.Options = opts
284+
g.Provider = ghcnx
285+
g.TargetNamespace = targetNS
286+
g.TargetRefName = targetRefName
287+
g.PRNumber = number
288+
g.SHA = sha
289+
290+
defer g.TearDown(ctx, t)
291+
defer func() {
292+
if os.Getenv("TEST_NOCLEANUP") != "true" {
293+
g.Logger.Infof("Deleting Ref %s", tempBaseBranchRef)
294+
_, err := ghcnx.Client().Git.DeleteRef(ctx, opts.Organization, opts.Repo, tempBaseBranchRef)
295+
assert.NilError(t, err)
296+
}
297+
}()
298+
299+
mergeResult, _, err := g.Provider.Client().PullRequests.Merge(ctx, opts.Organization, opts.Repo, g.PRNumber, g.CommitTitle, &github.PullRequestOptions{
300+
MergeMethod: "rebase",
301+
SHA: sha,
302+
})
303+
assert.NilError(t, err)
304+
g.Logger.Infof("Pull request %d has been merged", g.PRNumber)
305+
306+
// wait for API to reflect this PR in response
307+
time.Sleep(10 * time.Second)
308+
309+
mergedSHA := mergeResult.GetSHA()
310+
311+
_, _, err = g.Provider.Client().Repositories.CreateComment(ctx, opts.Organization, opts.Repo, mergedSHA, &github.RepositoryComment{Body: github.Ptr("/retest pipelinerun-pr-number-variable branch:" + tempBaseBranch)})
312+
assert.NilError(t, err)
313+
g.Logger.Infof("Comment %s has been created", mergedSHA)
314+
315+
waitOpts := twait.Opts{
316+
RepoName: g.TargetNamespace,
317+
Namespace: g.TargetNamespace,
318+
MinNumberStatus: 2,
319+
PollTimeout: twait.DefaultTimeout,
320+
TargetSHA: mergedSHA,
321+
}
322+
323+
err = twait.UntilPipelineRunHasReason(ctx, g.Cnx.Clients, tektonv1.PipelineRunReasonSuccessful, waitOpts)
324+
assert.NilError(t, err)
325+
326+
regex := regexp.MustCompile(fmt.Sprintf("I don't know my PR number is %d", g.PRNumber))
327+
328+
// because there are two pipeline runs were created due to pull request merge, we need the one which is triggered on retest comment.
329+
err = twait.RegexpMatchingInPodLog(ctx, g.Cnx, g.TargetNamespace,
330+
fmt.Sprintf("pipelinesascode.tekton.dev/event-type=%s",
331+
opscomments.RetestSingleCommentEventType.String()),
332+
"step-task", *regex, "", 2)
333+
assert.NilError(t, err)
334+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
apiVersion: tekton.dev/v1beta1
3+
kind: PipelineRun
4+
metadata:
5+
name: "pipelinerun-pr-number-variable"
6+
annotations:
7+
pipelinesascode.tekton.dev/target-namespace: "\\ .TargetNamespace //"
8+
pipelinesascode.tekton.dev/on-target-branch: "[\\ .TargetBranch //]"
9+
pipelinesascode.tekton.dev/on-event: "[push]"
10+
spec:
11+
pipelineSpec:
12+
tasks:
13+
- name: task
14+
displayName: "The Task name is Task"
15+
taskSpec:
16+
steps:
17+
- name: task
18+
image: registry.access.redhat.com/ubi10/ubi-micro
19+
command: ["/bin/echo", "I don't know my PR number is {{ pull_request_number }}"]

0 commit comments

Comments
 (0)