Skip to content

Commit

Permalink
Add GitOps comments functionality to push request
Browse files Browse the repository at this point in the history
This commit enables PAC users to include
GitOps comments for push request.
Supported GitOps comments are
- /test
- /retest
- /cancel
- /test prname
- /retest prname
- /cancel prname

Signed-off-by: Savita Ashture <sashture@redhat.com>
  • Loading branch information
savitaashture committed Oct 10, 2023
1 parent 232c143 commit fb727b1
Show file tree
Hide file tree
Showing 15 changed files with 473 additions and 84 deletions.
8 changes: 4 additions & 4 deletions docs/content/docs/guide/running.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ entire suite of checks once again.

![github apps rerun check](/images/github-apps-rerun-checks.png)

### Gitops command on pull or merge request
### Gitops command on push, pull or merge request

If you are targeting a pull or merge request you can use `GitOps` comment
If you are targeting a push, pull or merge request you can use `GitOps` comment
inside your pull request, to restart all or specific Pipelines.

For example you want to restart all your pipeline you can add a comment starting
For example, you want to restart all your pipeline you can add a comment starting
with `/retest` and all PipelineRun attached to that pull or merge request will be
restarted :

Expand All @@ -146,7 +146,7 @@ roses are red, violets are blue. pipeline are bound to flake by design.
You can cancel a running PipelineRun by commenting on the PullRequest.

For example if you want to cancel all your PipelinerRuns you can add a comment starting
with `/cancel` and all PipelineRun attached to that pull or merge request will be cancelled.
with `/cancel` and all PipelineRun attached to that push, pull or merge request will be cancelled.

Example :

Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/install/github_apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Alternatively, you could set up manually by following the steps [here](#setup-ma
* Check run
* Check suite
* Issue comment
* Commit comment
* Pull request
* Push

Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/tknpac/bootstrap/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func generateManifest(opts *bootstrapOpts) ([]byte, error) {
"check_run",
"check_suite",
"issue_comment",
"commit_comment",
"pull_request",
"push",
},
Expand Down
80 changes: 74 additions & 6 deletions pkg/pipelineascode/cancel_pipelinerun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ var (
URL: "https://github.com/fooorg/foo",
},
}
fooRepoLabelsForPush = map[string]string{
keys.URLRepository: formatting.CleanValueKubernetes("foo"),
keys.SHA: formatting.CleanValueKubernetes("foosha"),
}
fooRepoLabels = map[string]string{
keys.URLRepository: formatting.CleanValueKubernetes("foo"),
keys.SHA: formatting.CleanValueKubernetes("foosha"),
Expand Down Expand Up @@ -66,12 +70,6 @@ func TestCancelPipelinerun(t *testing.T) {
pipelineRuns []*pipelinev1.PipelineRun
cancelledPipelineRuns map[string]bool
}{
{
name: "not a pull request event",
event: &info.Event{
TriggerTarget: "push",
},
},
{
name: "cancel running",
event: &info.Event{
Expand Down Expand Up @@ -203,6 +201,76 @@ func TestCancelPipelinerun(t *testing.T) {
repo: fooRepo,
cancelledPipelineRuns: map[string]bool{},
},
{
name: "cancel running for push event",
event: &info.Event{
Repository: "foo",
SHA: "foosha",
TriggerTarget: "push",
State: info.State{
CancelPipelineRuns: true,
},
},
pipelineRuns: []*pipelinev1.PipelineRun{
{
ObjectMeta: metav1.ObjectMeta{
Name: "pr-foo",
Namespace: "foo",
Labels: fooRepoLabelsForPush,
},
Spec: pipelinev1.PipelineRunSpec{},
},
},
repo: fooRepo,
cancelledPipelineRuns: map[string]bool{
"pr-foo": true,
},
},
{
name: "cancel a specific run for push event",
event: &info.Event{
Repository: "foo",
SHA: "foosha",
TriggerTarget: "push",
State: info.State{
CancelPipelineRuns: true,
TargetCancelPipelineRun: "pr-foo-abc",
},
},
pipelineRuns: []*pipelinev1.PipelineRun{
{
ObjectMeta: metav1.ObjectMeta{
Name: "pr-foo",
Namespace: "foo",
Labels: fooRepoLabelsForPush,
Annotations: fooRepoAnnotations,
},
Spec: pipelinev1.PipelineRunSpec{},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "pr-foo-abc-123",
Namespace: "foo",
Labels: fooRepoLabelsPrFooAbc,
Annotations: fooRepoAnnotationsPrFooAbc,
},
Spec: pipelinev1.PipelineRunSpec{},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "pr-foo-pqr",
Namespace: "foo",
Labels: fooRepoLabelsForPush,
Annotations: fooRepoAnnotations,
},
Spec: pipelinev1.PipelineRunSpec{},
},
},
repo: fooRepo,
cancelledPipelineRuns: map[string]bool{
"pr-foo-abc-123": true,
},
},
}

for _, tt := range tests {
Expand Down
21 changes: 12 additions & 9 deletions pkg/pipelineascode/cancel_pipelineruns.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@ var cancelMergePatch = map[string]interface{}{
}

func (p *PacRun) cancelPipelineRuns(ctx context.Context, repo *v1alpha1.Repository) error {
if p.event.TriggerTarget != "pull_request" {
msg := fmt.Sprintf("not a pullRequest event, event: %v", p.event.TriggerTarget)
p.eventEmitter.EmitMessage(repo, zap.WarnLevel, "RepositoryEvent", msg)
return nil
}
labelSelector := getLabelSelector(map[string]string{
keys.URLRepository: formatting.CleanValueKubernetes(p.event.Repository),
keys.SHA: formatting.CleanValueKubernetes(p.event.SHA),
keys.PullRequest: strconv.Itoa(p.event.PullRequestNumber),
})

prs, err := p.run.Clients.Tekton.TektonV1().PipelineRuns(repo.Namespace).List(ctx, metav1.ListOptions{
LabelSelector: getLabelSelector(map[string]string{
if p.event.TriggerTarget == "push" {
labelSelector = getLabelSelector(map[string]string{
keys.URLRepository: formatting.CleanValueKubernetes(p.event.Repository),
keys.SHA: formatting.CleanValueKubernetes(p.event.SHA),
keys.PullRequest: strconv.Itoa(p.event.PullRequestNumber),
}),
})
}

prs, err := p.run.Clients.Tekton.TektonV1().PipelineRuns(repo.Namespace).List(ctx, metav1.ListOptions{
LabelSelector: labelSelector,
})
if err != nil {
return fmt.Errorf("failed to list pipelineRuns : %w", err)
Expand Down
13 changes: 13 additions & 0 deletions pkg/provider/github/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ func detectTriggerTypeFromPayload(ghEventType string, eventInt any) (info.Trigge
return info.TriggerTypeCheckRunRerequested, ""
}
return "", fmt.Sprintf("check_run: unsupported action \"%s\"", event.GetAction())
case *github.CommitCommentEvent:
if event.GetAction() == "created" {
if provider.IsTestRetestComment(event.GetComment().GetBody()) {
return info.TriggerTypeRetest, ""
}
if provider.IsOkToTestComment(event.GetComment().GetBody()) {
return info.TriggerTypeOkToTest, ""
}
if provider.IsCancelComment(event.GetComment().GetBody()) {
return info.TriggerTypeCancel, ""
}
}
return "", fmt.Sprintf("commit_comment: unsupported action \"%s\"", event.GetAction())
}
return "", fmt.Sprintf("github: event \"%v\" is not supported", ghEventType)
}
44 changes: 41 additions & 3 deletions pkg/provider/github/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,11 @@ func TestProvider_Detect(t *testing.T) {
processReq: true,
},
{
name: "unsupported Event",
name: "invalid commit_comment Event",
event: github.CommitCommentEvent{
Action: github.String("something"),
},
eventType: "commit_comment",
wantReason: "event \"commit_comment\" is not supported",
isGH: true,
processReq: false,
},
Expand Down Expand Up @@ -228,7 +227,7 @@ func TestProvider_Detect(t *testing.T) {
processReq: true,
},
{
name: "issue comment Event with retest",
name: "issue comment Event with cancel comment ",
event: github.IssueCommentEvent{
Action: github.String("created"),
Issue: &github.Issue{
Expand All @@ -246,6 +245,45 @@ func TestProvider_Detect(t *testing.T) {
isGH: true,
processReq: true,
},
{
name: "commit comment event with cancel comment",
event: github.CommitCommentEvent{
Action: github.String("created"),
Installation: &github.Installation{
ID: github.Int64(123),
},
Comment: &github.RepositoryComment{Body: github.String("/cancel")},
},
eventType: "commit_comment",
isGH: true,
processReq: true,
},
{
name: "commit comment Event with retest",
event: github.CommitCommentEvent{
Action: github.String("created"),
Installation: &github.Installation{
ID: github.Int64(123),
},
Comment: &github.RepositoryComment{Body: github.String("/retest")},
},
eventType: "commit_comment",
isGH: true,
processReq: true,
},
{
name: "commit comment Event with test",
event: github.CommitCommentEvent{
Action: github.String("created"),
Installation: &github.Installation{
ID: github.Int64(123),
},
Comment: &github.RepositoryComment{Body: github.String("/test")},
},
eventType: "commit_comment",
isGH: true,
processReq: true,
},
}

for _, tt := range tests {
Expand Down
30 changes: 30 additions & 0 deletions pkg/provider/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,33 @@ func uniqueRepositoryID(repoIDs []int64, id int64) []int64 {
}
return r
}

// listBranches list all branches in the repo and set the default branch for a matching branch commit SHA.
func (v *Provider) listBranches(ctx context.Context, runevent *info.Event) error {
if v.Client == nil {
return fmt.Errorf("no github client has been initialized, " +
"exiting... (hint: did you forget setting a secret on your repo?)")
}

opt := github.ListOptions{PerPage: v.paginedNumber}
for {
branchInfo, resp, err := v.Client.Repositories.ListBranches(ctx, runevent.Organization, runevent.Repository,
&github.BranchListOptions{
ListOptions: opt,
})
if err != nil {
return err
}
for _, b := range branchInfo {
if b.Commit.GetSHA() == runevent.SHA {
runevent.DefaultBranch = b.GetName()
break
}
}
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
return nil
}
33 changes: 33 additions & 0 deletions pkg/provider/github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,3 +998,36 @@ func TestCreateToken(t *testing.T) {
assert.Equal(t, strings.Contains(err.Error(), "could not refresh installation id 1234567's token"), true)
}
}

func TestListBranches(t *testing.T) {
runEvent := &info.Event{
Organization: "pushrequestowner",
Repository: "pushrequestrepository",
SHA: "sha1",
InstallationID: int64(1234567),
}
fakeclient, mux, _, teardown := ghtesthelper.SetupGH()
defer teardown()

mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/branches",
runEvent.Organization, runEvent.Repository), func(rw http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(rw, `[{
"name": "test1",
"commit": {
"sha": "sha1"
}
}, {
"name": "test2",
"commit": {
"sha": "sha2"
}
}]`)
assert.NilError(t, err)
})

ctx, _ := rtesting.SetupFakeContext(t)
provider := &Provider{Client: fakeclient}
err := provider.listBranches(ctx, runEvent)
assert.NilError(t, err)
assert.Equal(t, runEvent.DefaultBranch, "test1")
}
39 changes: 39 additions & 0 deletions pkg/provider/github/parse_payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ func (v *Provider) processEvent(ctx context.Context, event *info.Event, eventInt
if err != nil {
return nil, err
}
case *github.CommitCommentEvent:
if v.Client == nil {
return nil, fmt.Errorf("gitops style comments operation is only supported with github apps integration")
}
return v.handleCommitCommentEvent(ctx, gitEvent)
case *github.PushEvent:
processedEvent.Organization = gitEvent.GetRepo().GetOwner().GetLogin()
processedEvent.Repository = gitEvent.GetRepo().GetName()
Expand Down Expand Up @@ -370,3 +375,37 @@ func (v *Provider) handleIssueCommentEvent(ctx context.Context, event *github.Is
v.Logger.Infof("issue_comment: pipelinerun %s on %s/%s#%d has been requested", action, runevent.Organization, runevent.Repository, runevent.PullRequestNumber)
return v.getPullRequest(ctx, runevent)
}

func (v *Provider) handleCommitCommentEvent(ctx context.Context, event *github.CommitCommentEvent) (*info.Event, error) {
action := "recheck"
runevent := info.NewEvent()
runevent.Organization = event.GetRepo().GetOwner().GetLogin()
runevent.Repository = event.GetRepo().GetName()
runevent.Sender = event.GetSender().GetLogin()
runevent.URL = event.GetRepo().GetHTMLURL()
runevent.SHA = event.GetComment().GetCommitID()
runevent.HeadURL = runevent.URL
runevent.BaseURL = runevent.HeadURL
runevent.EventType = "push"
runevent.TriggerTarget = "push"

if err := v.listBranches(ctx, runevent); err != nil {
return runevent, err
}

// value for runevent.DefaultBranch set by listBranches function
runevent.HeadBranch = runevent.DefaultBranch
runevent.BaseBranch = runevent.DefaultBranch

// if it is a /test or /retest comment with pipelinerun name figure out the pipelinerun name
if provider.IsTestRetestComment(event.GetComment().GetBody()) {
runevent.TargetTestPipelineRun = provider.GetPipelineRunFromTestComment(event.GetComment().GetBody())
}
if provider.IsCancelComment(event.GetComment().GetBody()) {
action = "cancellation"
runevent.CancelPipelineRuns = true
runevent.TargetCancelPipelineRun = provider.GetPipelineRunFromCancelComment(event.GetComment().GetBody())
}
v.Logger.Infof("commit_comment: pipelinerun %s on %s/%s#%s has been requested", action, runevent.Organization, runevent.Repository, runevent.SHA)
return runevent, nil
}
Loading

0 comments on commit fb727b1

Please sign in to comment.