Skip to content

Commit a0cac19

Browse files
committed
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 3be8306 commit a0cac19

5 files changed

Lines changed: 234 additions & 6 deletions

File tree

pkg/provider/github/github.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type Provider struct {
5959
skippedRun
6060
triggerEvent string
6161
cachedChangedFiles *changedfiles.ChangedFiles
62+
commitInfo *github.Commit
6263
}
6364

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

409416
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: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,23 @@ package test
55
import (
66
"context"
77
"fmt"
8+
"net/http"
9+
"os"
810
"regexp"
911
"testing"
12+
"time"
1013

1114
"github.com/google/go-github/v81/github"
1215
"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"
1318
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/cctx"
1419
tgithub "github.com/openshift-pipelines/pipelines-as-code/test/pkg/github"
1520
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/options"
21+
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/payload"
1622
twait "github.com/openshift-pipelines/pipelines-as-code/test/pkg/wait"
1723
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
24+
"github.com/tektoncd/pipeline/pkg/names"
1825
"gotest.tools/v3/assert"
1926
corev1 "k8s.io/api/core/v1"
2027
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -217,3 +224,112 @@ func TestGithubSecondPushRequestGitOpsCommentCancel(t *testing.T) {
217224
}
218225
assert.Assert(t, cancelled, "No cancelled pipeline run found")
219226
}
227+
228+
func TestGithubPullRequestRetestPullRequestNumberSubstitution(t *testing.T) {
229+
targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns")
230+
ctx := context.Background()
231+
g := &tgithub.PRTest{}
232+
233+
ctx, runcnx, opts, ghcnx, err := tgithub.Setup(ctx, g.SecondController, g.Webhook)
234+
assert.NilError(t, err)
235+
g.Logger = runcnx.Clients.Log
236+
237+
g.CommitTitle = fmt.Sprintf("Testing %s with Github APPS integration on %s", g.Label, targetNS)
238+
g.Logger.Info(g.CommitTitle)
239+
240+
repoinfo, resp, err := ghcnx.Client().Repositories.Get(ctx, opts.Organization, opts.Repo)
241+
assert.NilError(t, err)
242+
if resp != nil && resp.StatusCode == http.StatusNotFound {
243+
t.Errorf("Repository %s not found in %s", opts.Organization, opts.Repo)
244+
}
245+
246+
if g.Options.Settings.Github != nil {
247+
opts.Settings = g.Options.Settings
248+
}
249+
err = tgithub.CreateCRD(ctx, t, repoinfo, runcnx, opts, targetNS)
250+
assert.NilError(t, err)
251+
252+
tempBaseBranch := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("temp-base-branch")
253+
tempBaseBranchRef := fmt.Sprintf("refs/heads/%s", tempBaseBranch)
254+
255+
// Get the SHA of the default branch
256+
defaultBranchRef, _, err := ghcnx.Client().Git.GetRef(ctx, opts.Organization, opts.Repo, fmt.Sprintf("refs/heads/%s", repoinfo.GetDefaultBranch()))
257+
assert.NilError(t, err)
258+
259+
// Create the temporary base branch
260+
_, _, err = ghcnx.Client().Git.CreateRef(ctx, opts.Organization, opts.Repo, github.CreateRef{
261+
Ref: tempBaseBranchRef,
262+
SHA: *defaultBranchRef.Object.SHA,
263+
})
264+
assert.NilError(t, err)
265+
266+
yamlEntries := map[string]string{".tekton/pipelinerun-pr-number-variable.yaml": "testdata/pipelinerun-pr-number-variable.yaml"}
267+
268+
entries, err := payload.GetEntries(yamlEntries, targetNS, tempBaseBranchRef, triggertype.Push.String(), map[string]string{})
269+
assert.NilError(t, err)
270+
271+
targetRefName := fmt.Sprintf("refs/heads/%s",
272+
names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test"))
273+
274+
sha, vref, err := tgithub.PushFilesToRef(ctx, ghcnx.Client(), g.CommitTitle, tempBaseBranchRef, targetRefName,
275+
opts.Organization, opts.Repo, entries)
276+
assert.NilError(t, err)
277+
278+
g.Logger.Infof("Commit %s has been created and pushed to %s", sha, vref.GetURL())
279+
number, err := tgithub.PRCreate(ctx, runcnx, ghcnx, opts.Organization,
280+
opts.Repo, targetRefName, tempBaseBranchRef, g.CommitTitle)
281+
assert.NilError(t, err)
282+
283+
g.Cnx = runcnx
284+
g.Options = opts
285+
g.Provider = ghcnx
286+
g.TargetNamespace = targetNS
287+
g.TargetRefName = targetRefName
288+
g.PRNumber = number
289+
g.SHA = sha
290+
291+
defer g.TearDown(ctx, t)
292+
defer func() {
293+
if os.Getenv("TEST_NOCLEANUP") != "true" {
294+
g.Logger.Infof("Deleting Ref %s", tempBaseBranchRef)
295+
_, err := ghcnx.Client().Git.DeleteRef(ctx, opts.Organization, opts.Repo, tempBaseBranchRef)
296+
assert.NilError(t, err)
297+
}
298+
}()
299+
300+
mergeResult, _, err := g.Provider.Client().PullRequests.Merge(ctx, opts.Organization, opts.Repo, g.PRNumber, g.CommitTitle, &github.PullRequestOptions{
301+
MergeMethod: "rebase",
302+
SHA: sha,
303+
})
304+
assert.NilError(t, err)
305+
g.Logger.Infof("Pull request %d has been merged", g.PRNumber)
306+
307+
// wait for API to reflect this PR in response
308+
time.Sleep(10 * time.Second)
309+
310+
mergedSHA := mergeResult.GetSHA()
311+
312+
_, _, err = g.Provider.Client().Repositories.CreateComment(ctx, opts.Organization, opts.Repo, mergedSHA, &github.RepositoryComment{Body: github.Ptr("/retest pipelinerun-pr-number-variable branch:" + tempBaseBranch)})
313+
assert.NilError(t, err)
314+
g.Logger.Infof("Comment %s has been created", mergedSHA)
315+
316+
waitOpts := twait.Opts{
317+
RepoName: g.TargetNamespace,
318+
Namespace: g.TargetNamespace,
319+
MinNumberStatus: 2,
320+
PollTimeout: twait.DefaultTimeout,
321+
TargetSHA: mergedSHA,
322+
}
323+
324+
err = twait.UntilPipelineRunHasReason(ctx, g.Cnx.Clients, tektonv1.PipelineRunReasonSuccessful, waitOpts)
325+
assert.NilError(t, err)
326+
327+
regex := regexp.MustCompile(fmt.Sprintf("I don't know my PR number is %d", g.PRNumber))
328+
329+
// because there are two pipeline runs were created due to pull request merge, we need the one which is triggered on retest comment.
330+
err = twait.RegexpMatchingInPodLog(ctx, g.Cnx, g.TargetNamespace,
331+
fmt.Sprintf("pipelinesascode.tekton.dev/event-type=%s",
332+
opscomments.RetestSingleCommentEventType.String()),
333+
"step-task", *regex, "", 2)
334+
assert.NilError(t, err)
335+
}
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)