Repository: openshift-pipelines/pipelines-as-code
Pull request #86 (2021-05-26 07:27:47)

Add support for /ok-to-test
Add support for /ok-to-test

If we have a user issue a /ok-to-test in a comment and the comment sender is
allowed to run the CI, it will allow the current PR to be run on CI even
tho the pr sender is not allowed.

chmouel: /retest

Patch:
diff --git a/README.md b/README.md
index cc88041c..75bd99db 100644
--- a/README.md
+++ b/README.md
@@ -237,6 +237,8 @@ approvers:
 
 then the user `approved` will be allowed.
 
+If the sender of a PR is not allowed to run CI but one of allowed user issue a `/ok-to-test` in any line of a comment the PR will be allowed to run CI.
+
 If the user is allowed, `Pipelines as Code` will start creating the `PipelineRun` in the target user namespace.
 
 The user can follow the execution of your pipeline with the
diff --git a/config/400-trigger-eventlistenner.yaml b/config/400-trigger-eventlistenner.yaml
index a16980d0..b76117c3 100644
--- a/config/400-trigger-eventlistenner.yaml
+++ b/config/400-trigger-eventlistenner.yaml
@@ -31,6 +31,28 @@ spec:
       template:
         ref: openshift-pipelines-ascode-template-retest-comment
 
+    # When you have a /ok-to-test in a comment to allow CI on a non owner sender
+    - name: github-issue-comment-retest
+      bindings:
+        - ref: openshift-pipelines-ascode-bindings-ok-to-test-comment
+      interceptors:
+        - github:
+            secretRef:
+              secretName: "github-app-secret"
+              secretKey: "webhook.secret"
+            eventTypes:
+              - issue_comment
+        - cel:
+            filter: >-
+              body.action == 'created' &&
+              'pull_request' in body.issue &&
+              body.issue.state == 'open' &&
+              'installation' in body &&
+              body.comment.body.matches('(^|\\r\\n)/ok-to-test([ ]*$|$|\\r\\n)')
+      template:
+        # Using retest comment since it was the first one
+        ref: openshift-pipelines-ascode-template-retest-comment
+
     # Branch push using different binding but same triggertemplate as pullreq
     - name: github-branch-push
       bindings:
diff --git a/config/401-trigger-issue-recheck.yaml b/config/401-trigger-issue-recheck.yaml
index d21154ba..87199860 100644
--- a/config/401-trigger-issue-recheck.yaml
+++ b/config/401-trigger-issue-recheck.yaml
@@ -11,6 +11,8 @@ spec:
   params:
     - name: action
       value: $(body.action)
+    - name: trigger_target
+      value: "issue-recheck"
     - name: event_type
       value: $(header.X-GitHub-Event)
     - name: owner
@@ -39,6 +41,7 @@ spec:
     - name: repository
     - name: pull_request_number
     - name: installation_id
+    - name: trigger_target
   resourcetemplates:
     - apiVersion: tekton.dev/v1beta1
       kind: PipelineRun
@@ -62,8 +65,11 @@ spec:
             value: $(tt.params.pull_request_number)
           - name: installation_id
             value: $(tt.params.installation_id)
+          - name: trigger_target
+            value: $(tt.params.trigger_target)
         pipelineSpec:
           params:
+            - name: trigger_target
             - name: action
             - name: event_type
             - name: owner
@@ -98,6 +104,8 @@ spec:
                     type: string
                   - name: token
                     type: string
+                  - name: trigger_target
+                    type: string
                 steps:
                   - name: apply-and-launch
                     image: "ko://github.com/openshift-pipelines/pipelines-as-code/cmd/pipelines-as-code"
@@ -122,10 +130,14 @@ spec:
                         }
                       }
                       EOF
-                      pipelines-as-code --payload-file=/tmp/payload.json --token="$(params.token)" --webhook-type="$(params.event_type)"
+                      pipelines-as-code --trigger-action=$(params.trigger_target) \
+                        --payload-file=/tmp/payload.json --token="$(params.token)" --webhook-type="$(params.event_type)"
+
               params:
                 - name: action
                   value: "$(params.action)"
+                - name: trigger_target
+                  value: "$(params.trigger_target)"
                 - name: event_type
                   value: "$(params.event_type)"
                 - name: owner
diff --git a/config/402-trigger-pull-request.yaml b/config/402-trigger-pull-request.yaml
index e0ab318e..39bd810a 100644
--- a/config/402-trigger-pull-request.yaml
+++ b/config/402-trigger-pull-request.yaml
@@ -9,6 +9,8 @@ metadata:
     app.kubernetes.io/part-of: openshift-pipelines-ascode
 spec:
   params:
+    - name: trigger_target
+      value: "pull-request"
     - name: "event_type"
       value: $(header.X-GitHub-Event)
     - name: "owner"
@@ -51,6 +53,7 @@ spec:
     - name: sha
     - name: head_ref
     - name: installation_id
+    - name: trigger_target
   resourcetemplates:
     - apiVersion: tekton.dev/v1beta1
       kind: PipelineRun
@@ -64,6 +67,8 @@ spec:
         params:
           - name: event_type
             value: $(tt.params.event_type)
+          - name: trigger_target
+            value: $(tt.params.trigger_target)
           - name: owner
             value: $(tt.params.owner)
           - name: repository
@@ -85,6 +90,7 @@ spec:
         pipelineSpec:
           params:
             - name: event_type
+            - name: trigger_target
             - name: owner
             - name: repository
             - name: default_branch
@@ -110,6 +116,8 @@ spec:
               runAfter: [get-token]
               taskSpec:
                 params:
+                  - name: trigger_target
+                    type: string
                   - name: event_type
                     type: string
                   - name: owner
@@ -158,10 +166,13 @@ spec:
                           }
                       }
                       EOF
-                      pipelines-as-code --payload-file=/tmp/payload.json --token="$(params.token)" --webhook-type="$(params.event_type)"
+                      pipelines-as-code --trigger-action=$(params.trigger_target) \
+                        --payload-file=/tmp/payload.json --token="$(params.token)" --webhook-type="$(params.event_type)"
               params:
                 - name: event_type
                   value: $(params.event_type)
+                - name: trigger_target
+                  value: $(params.trigger_target)
                 - name: owner
                   value: $(params.owner)
                 - name: repository
@@ -180,6 +191,7 @@ spec:
                   value: $(params.head_ref)
                 - name: token
                   value: "$(tasks.get-token.results.token)"
+
         workspaces:
           - name: secrets
             secret:
diff --git a/config/403-trigger-push.yaml b/config/403-trigger-push.yaml
index 618fe206..5b236e01 100644
--- a/config/403-trigger-push.yaml
+++ b/config/403-trigger-push.yaml
@@ -11,6 +11,8 @@ spec:
   params:
     - name: "event_type"
       value: $(header.X-GitHub-Event)
+    - name: "trigger_target"
+      value: "push"
     - name: "owner"
       value: $(body.repository.owner.login)
     - name: "repository"
diff --git a/pkg/cmd/pipelineascode/pipelineascode.go b/pkg/cmd/pipelineascode/pipelineascode.go
index 62a4a3c8..e40d98d2 100644
--- a/pkg/cmd/pipelineascode/pipelineascode.go
+++ b/pkg/cmd/pipelineascode/pipelineascode.go
@@ -66,6 +66,7 @@ func Command(p cli.Params) *cobra.Command {
 	cmd.Flags().StringVarP(&opts.RunInfo.Sender, "webhook-sender", "", os.Getenv("PAC_Sender"), "Sender for the commit/pr")
 	cmd.Flags().StringVarP(&opts.RunInfo.URL, "webhook-url", "", os.Getenv("PAC_URL"), "URL of the repository to test")
 	cmd.Flags().StringVarP(&opts.RunInfo.EventType, "webhook-type", "", os.Getenv("PAC_EVENT_TYPE"), "Payload event type as set from Github")
+	cmd.Flags().StringVarP(&opts.RunInfo.TriggerTarget, "trigger-target", "", os.Getenv("PAC_TRIGGER_TARGET"), "The trigger target from where this event comes from")
 
 	cmd.Flags().StringVarP(&opts.Payload, "payload", "", os.Getenv("PAC_PAYLOAD"), "The payload from webhook as string")
 	cmd.Flags().StringVarP(&opts.PayloadFile, "payload-file", "", os.Getenv("PAC_PAYLOAD_FILE"), "A file containing the webhook payload")
@@ -79,7 +80,7 @@ func getRunInfoFromArgsOrPayload(ctx context.Context, cs *cli.Clients, payload s
 		return &webvcs.RunInfo{}, fmt.Errorf("no payload or not enough params set properly")
 	}
 
-	payloadinfo, err := cs.GithubClient.ParsePayload(ctx, cs.Log, runinfo.EventType, payload)
+	payloadinfo, err := cs.GithubClient.ParsePayload(ctx, cs.Log, runinfo.EventType, runinfo.TriggerTarget, payload)
 	if err != nil {
 		return &webvcs.RunInfo{}, err
 	}
diff --git a/pkg/config/annotation_matcher.go b/pkg/config/annotation_matcher.go
index 2757bcd5..044527ad 100644
--- a/pkg/config/annotation_matcher.go
+++ b/pkg/config/annotation_matcher.go
@@ -2,6 +2,7 @@ package config
 
 import (
 	"errors"
+	"fmt"
 	"regexp"
 	"strings"
 
@@ -71,7 +72,7 @@ func MatchPipelinerunByAnnotation(pruns []*v1beta1.PipelineRun, cs *cli.Clients,
 		return prun, nil
 	}
 	// TODO: more descriptive error message
-	return nil, errors.New("cannot match any pipeline")
+	return nil, fmt.Errorf("cannot match any pipeline on EventType: %s, TargetBranch: %s", runinfo.EventType, runinfo.BaseBranch)
 }
 
 func matchOnAnnotation(targetBranchAnnotation string, match string) (bool, error) {
diff --git a/pkg/pipelineascode/acl.go b/pkg/pipelineascode/acl.go
index 47f5f9c6..5bea2bcf 100644
--- a/pkg/pipelineascode/acl.go
+++ b/pkg/pipelineascode/acl.go
@@ -4,21 +4,50 @@ import (
 	"context"
 	"strings"
 
+	"github.com/google/go-github/v34/github"
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/webvcs"
 	"sigs.k8s.io/yaml"
 )
 
+var okToTestCommentRegexp = `(^|\n)/ok-to-test(\r\n|$)`
+
 // OwnersConfig prow owner, only supporting approvers or reviewers in yaml
 type OwnersConfig struct {
 	Approvers []string `json:"approvers,omitempty"`
 	Reviewers []string `json:"reviewers,omitempty"`
 }
 
-// aclCheck check if we are allowed to run pipeline
+// allowedOkToTestFromAnOwner Goes on evry comments in a pull-request and sess
+// if there is a /ok-to-test in there running an aclCheck again on the commment
+// Sender if she is an OWNER and then allow it to run CI.
+// TODO: pull out the github logic from there in an agnostic way.
+func allowedOkToTestFromAnOwner(ctx context.Context, cs *cli.Clients, runinfo *webvcs.RunInfo) (bool, error) {
+	rinfo := &webvcs.RunInfo{}
+	runinfo.DeepCopyInto(rinfo)
+	rinfo.EventType = ""
+	rinfo.TriggerTarget = ""
+	rinfo.URL = rinfo.Event.(*github.IssueCommentEvent).Issue.GetPullRequestLinks().GetHTMLURL()
+	comments, err := cs.GithubClient.GetStringPullRequestComment(ctx, rinfo, okToTestCommentRegexp)
+	if err != nil {
+		return false, err
+	}
+
+	for _, comment := range comments {
+		rinfo.Sender = comment.User.GetLogin()
+		allowed, err := aclCheck(ctx, cs, rinfo)
+		if err != nil {
+			return false, err
+		}
+		if allowed {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+// aclCheck check if we are allowed to run the pipeline on that PR
 func aclCheck(ctx context.Context, cs *cli.Clients, runinfo *webvcs.RunInfo) (bool, error) {
-	// If the user who submitted the PR is the same as the Owner of the repo
-	// then allow it.
 	if runinfo.Owner == runinfo.Sender {
 		return true, nil
 	}
@@ -54,5 +83,15 @@ func aclCheck(ctx context.Context, cs *cli.Clients, runinfo *webvcs.RunInfo) (bo
 		}
 	}
 
+	if runinfo.TriggerTarget == "ok-to-test-comment" {
+		allowed, err := allowedOkToTestFromAnOwner(ctx, cs, runinfo)
+		if err != nil {
+			return false, err
+		}
+		if allowed {
+			return true, nil
+		}
+	}
+
 	return false, nil
 }
diff --git a/pkg/webvcs/github.go b/pkg/webvcs/github.go
index 4ede7e9a..fd082fbb 100644
--- a/pkg/webvcs/github.go
+++ b/pkg/webvcs/github.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"net/http"
 	"path"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -24,14 +25,16 @@ type GithubVCS struct {
 // RunInfo Information about current run
 type RunInfo struct {
 	BaseBranch    string // branch against where we are making the PR
-	HeadBranch    string // branch from where our SHA get tested
-	DefaultBranch string
 	CheckRunID    *int64
+	DefaultBranch string
+	Event         interface{}
 	EventType     string
+	HeadBranch    string // branch from where our SHA get tested
 	Owner         string
 	Repository    string
-	Sender        string
 	SHA           string
+	Sender        string
+	TriggerTarget string
 	URL           string
 	WebConsoleURL string
 }
@@ -41,7 +44,7 @@ func (r RunInfo) Check() error {
 	if r.SHA != "" && r.BaseBranch != "" &&
 		r.Repository != "" && r.DefaultBranch != "" &&
 		r.HeadBranch != "" && r.Owner != "" && r.URL != "" &&
-		r.Sender != "" && r.EventType != "" {
+		r.Sender != "" && r.EventType != "" && r.TriggerTarget != "" {
 		return nil
 	}
 	return fmt.Errorf("missing values in runInfo")
@@ -90,11 +93,20 @@ func (v GithubVCS) handleReRequestEvent(ctx context.Context, log *zap.SugaredLog
 	return v.getPullRequest(ctx, runinfo, prNumber)
 }
 
+func convertPullRequestURLtoNumber(pullRequest string) (int, error) {
+	prNumber, err := strconv.Atoi(path.Base(pullRequest))
+	if err != nil {
+		return -1, err
+	}
+	return prNumber, nil
+}
+
 func (v GithubVCS) handleIssueCommentEvent(ctx context.Context, log *zap.SugaredLogger, event *github.IssueCommentEvent) (RunInfo, error) {
 	runinfo := RunInfo{
 		Owner:      event.GetRepo().GetOwner().GetLogin(),
 		Repository: event.GetRepo().GetName(),
 	}
+
 	if !event.GetIssue().IsPullRequest() {
 		return RunInfo{}, fmt.Errorf("issue comment is not coming from a pull_request")
 	}
@@ -102,7 +114,7 @@ func (v GithubVCS) handleIssueCommentEvent(ctx context.Context, log *zap.Sugared
 	// We are getting the full URL so we have to get the last part to get the PR number,
 	// we don't have to care about URL query string/hash and other stuff because
 	// that comes up from the API.
-	prNumber, err := strconv.Atoi(path.Base(event.GetIssue().GetPullRequestLinks().GetHTMLURL()))
+	prNumber, err := convertPullRequestURLtoNumber(event.GetIssue().GetPullRequestLinks().GetHTMLURL())
 	if err != nil {
 		return RunInfo{}, err
 	}
@@ -132,7 +144,7 @@ func (v GithubVCS) getPullRequest(ctx context.Context, runinfo RunInfo, prNumber
 }
 
 // ParsePayload parse payload event
-func (v GithubVCS) ParsePayload(ctx context.Context, log *zap.SugaredLogger, eventType, payload string) (*RunInfo, error) {
+func (v GithubVCS) ParsePayload(ctx context.Context, log *zap.SugaredLogger, eventType, triggerTarget, payload string) (*RunInfo, error) {
 	var runinfo RunInfo
 	payload = payloadFix(payload)
 	event, err := github.ParseWebHook(eventType, []byte(payloadFix(payload)))
@@ -146,7 +158,7 @@ func (v GithubVCS) ParsePayload(ctx context.Context, log *zap.SugaredLogger, eve
 
 	switch event := event.(type) {
 	case *github.CheckRunEvent:
-		if event.GetAction() == "rerequested" {
+		if triggerTarget == "issue-recheck" {
 			runinfo, err = v.handleReRequestEvent(ctx, log, event)
 			if err != nil {
 				return &runinfo, err
@@ -184,6 +196,8 @@ func (v GithubVCS) ParsePayload(ctx context.Context, log *zap.SugaredLogger, eve
 	default:
 		return &runinfo, errors.New("this event is not supported")
 	}
+	runinfo.Event = event
+	runinfo.TriggerTarget = triggerTarget
 	return &runinfo, nil
 }
 
@@ -212,6 +226,29 @@ func (v GithubVCS) CheckSenderOrgMembership(ctx context.Context, runinfo *RunInf
 	return false, nil
 }
 
+// GetStringPullRequestComment return the comment if we find a regexp in one of
+// the comments text of a pull request
+func (v GithubVCS) GetStringPullRequestComment(ctx context.Context, runinfo *RunInfo, reg string) ([]*github.IssueComment, error) {
+	var ret []*github.IssueComment
+	prNumber, err := convertPullRequestURLtoNumber(runinfo.URL)
+	if err != nil {
+		return nil, err
+	}
+	comments, _, err := v.Client.Issues.ListComments(ctx, runinfo.Owner, runinfo.Repository,
+		prNumber, &github.IssueListCommentsOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	re := regexp.MustCompile(reg)
+	for _, v := range comments {
+		if string(re.Find([]byte(v.GetBody()))) != "" {
+			ret = append(ret, v)
+		}
+	}
+	return ret, nil
+}
+
 // GetTektonDir Get tekton directory from a repository
 func (v GithubVCS) GetTektonDir(ctx context.Context, path string, runinfo *RunInfo) ([]*github.RepositoryContent, error) {
 	fp, objects, resp, err := v.Client.Repositories.GetContents(ctx, runinfo.Owner,


Test patch:
diff --git a/config/404-trigger-retest-comment.yaml b/config/404-trigger-retest-comment.yaml
index 2a6cfac6..945e92a0 100644
--- a/config/404-trigger-retest-comment.yaml
+++ b/config/404-trigger-retest-comment.yaml
@@ -9,19 +9,22 @@ metadata:
     app.kubernetes.io/part-of: openshift-pipelines-ascode
 spec:
   params:
+    - name: trigger_target
+      value: "retest-comment"
     - name: action
       value: $(body.action)
     - name: event_type
       value: $(header.X-GitHub-Event)
     - name: owner
       value: $(body.repository.owner.login)
+    - name: sender
+      value: $(body.sender.login)
     - name: repository
       value: $(body.repository.name)
     - name: pull_request_url
       value: $(body.issue.pull_request.html_url)
     - name: installation_id
       value: $(body.installation.id)
-
 ---
 apiVersion: triggers.tekton.dev/v1alpha1
 kind: TriggerTemplate
@@ -36,9 +39,11 @@ spec:
     - name: action
     - name: event_type
     - name: owner
+    - name: sender
     - name: repository
     - name: pull_request_url
     - name: installation_id
+    - name: trigger_target
   resourcetemplates:
     - apiVersion: tekton.dev/v1beta1
       kind: PipelineRun
@@ -56,17 +61,23 @@ spec:
             value: $(tt.params.event_type)
           - name: owner
             value: $(tt.params.owner)
+          - name: sender
+            value: $(tt.params.sender)
           - name: repository
             value: $(tt.params.repository)
           - name: pull_request_url
             value: $(tt.params.pull_request_url)
           - name: installation_id
             value: $(tt.params.installation_id)
+          - name: trigger_target
+            value: $(tt.params.trigger_target)
         pipelineSpec:
           params:
             - name: action
+            - name: trigger_target
             - name: event_type
             - name: owner
+            - name: sender
             - name: repository
             - name: pull_request_url
             - name: installation_id
@@ -92,12 +103,16 @@ spec:
                     type: string
                   - name: owner
                     type: string
+                  - name: sender
+                    type: string
                   - name: repository
                     type: string
                   - name: pull_request_url
                     type: string
                   - name: token
                     type: string
+                  - name: trigger_target
+                    type: string
                 steps:
                   - name: apply-and-launch
                     image: "ko://github.com/openshift-pipelines/pipelines-as-code/cmd/pipelines-as-code"
@@ -110,6 +125,9 @@ spec:
                             "html_url": "$(params.pull_request_url)"
                           }
                         },
+                        "sender": {
+                           "login": "$(params.sender)"
+                         },
                         "repository": {
                             "name": "$(params.repository)",
                             "owner": {
@@ -118,14 +136,19 @@ spec:
                         }
                       }
                       EOF
-                      pipelines-as-code --payload-file=/tmp/payload.json --token="$(params.token)" --webhook-type="$(params.event_type)"
+                      pipelines-as-code --trigger-action=$(params.trigger_target) \
+                        --payload-file=/tmp/payload.json --token="$(params.token)" --webhook-type="$(params.event_type)"
               params:
                 - name: action
                   value: "$(params.action)"
+                - name: trigger_target
+                  value: "$(params.trigger_target)"
                 - name: event_type
                   value: "$(params.event_type)"
                 - name: owner
                   value: "$(params.owner)"
+                - name: sender
+                  value: "$(params.sender)"
                 - name: repository
                   value: "$(params.repository)"
                 - name: pull_request_url
diff --git a/config/405-trigger-ok-to-test-comment.yaml b/config/405-trigger-ok-to-test-comment.yaml
new file mode 100644
index 00000000..52bc8dba
--- /dev/null
+++ b/config/405-trigger-ok-to-test-comment.yaml
@@ -0,0 +1,27 @@
+---
+apiVersion: triggers.tekton.dev/v1alpha1
+kind: TriggerBinding
+metadata:
+  name: openshift-pipelines-ascode-bindings-ok-to-test-comment
+  namespace: pipelines-ascode
+  labels:
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: openshift-pipelines-ascode
+spec:
+  params:
+    - name: action
+      value: "$(body.action)"
+    - name: trigger_target
+      value: "ok-to-test-comment"
+    - name: event_type
+      value: $(header.X-GitHub-Event)
+    - name: owner
+      value: $(body.repository.owner.login)
+    - name: sender
+      value: $(body.sender.login)
+    - name: repository
+      value: $(body.repository.name)
+    - name: pull_request_url
+      value: $(body.issue.pull_request.html_url)
+    - name: installation_id
+      value: $(body.installation.id)
diff --git a/pkg/cmd/pipelineascode/pipelineascode_test.go b/pkg/cmd/pipelineascode/pipelineascode_test.go
index 01826bec..404d8252 100644
--- a/pkg/cmd/pipelineascode/pipelineascode_test.go
+++ b/pkg/cmd/pipelineascode/pipelineascode_test.go
@@ -60,7 +60,8 @@ func TestRunWrapPR(t *testing.T) {
 
 	options := &pacpkg.Options{
 		RunInfo: webvcs.RunInfo{
-			EventType: "pull_request",
+			EventType:     "pull_request",
+			TriggerTarget: "pull_request",
 		},
 	}
 	err := runWrap(ctx, options, cs, k8int)
@@ -94,6 +95,7 @@ func TestGetInfo(t *testing.T) {
 		HeadBranch:    "headRunInfoBranch",
 		Sender:        "ElSender",
 		EventType:     "pull_request",
+		TriggerTarget: "pull_request",
 	}
 
 	b, err := ioutil.ReadFile("testdata/pull_request.json")
@@ -126,7 +128,7 @@ func TestGetInfo(t *testing.T) {
 	}{
 		{
 			desc:    "Error on bad payload",
-			runinfo: webvcs.RunInfo{EventType: "pull_request"},
+			runinfo: webvcs.RunInfo{EventType: "pull_request", TriggerTarget: "pull_request"},
 			payload: "foo bar",
 			errmsg:  "invalid character",
 		},
@@ -147,7 +149,7 @@ func TestGetInfo(t *testing.T) {
 		},
 		{
 			desc:    "Missing values payload",
-			runinfo: webvcs.RunInfo{EventType: "pull_request"},
+			runinfo: webvcs.RunInfo{EventType: "pull_request", TriggerTarget: "pull_request"},
 			payload: missingValuesPayload,
 			errmsg:  "missing some values",
 		},
@@ -160,7 +162,7 @@ func TestGetInfo(t *testing.T) {
 		},
 		{
 			desc:           "Good payload",
-			runinfo:        webvcs.RunInfo{EventType: "pull_request"},
+			runinfo:        webvcs.RunInfo{EventType: "pull_request", TriggerTarget: "pull_request"},
 			payload:        goodPayload,
 			errmsg:         "",
 			branchShouldBe: "goodInfoBranch",
@@ -213,7 +215,7 @@ func TestRunWrap(t *testing.T) {
 		{
 			name: "good",
 			opts: &pacpkg.Options{
-				RunInfo:     webvcs.RunInfo{EventType: "pull_request"},
+				RunInfo:     webvcs.RunInfo{EventType: "pull_request", TriggerTarget: "pull_request"},
 				PayloadFile: "testdata/pull_request.json",
 			},
 		},
diff --git a/pkg/pipelineascode/acl_test.go b/pkg/pipelineascode/acl_test.go
index c47643ac..f986882e 100644
--- a/pkg/pipelineascode/acl_test.go
+++ b/pkg/pipelineascode/acl_test.go
@@ -6,12 +6,86 @@ import (
 	"net/http"
 	"testing"
 
+	"github.com/google/go-github/v34/github"
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
 	ghtesthelper "github.com/openshift-pipelines/pipelines-as-code/pkg/test/github"
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/webvcs"
 	rtesting "knative.dev/pkg/reconciler/testing"
 )
 
+func TestOkToTestComment(t *testing.T) {
+	tests := []struct {
+		name          string
+		commentsReply string
+		runinfo       *webvcs.RunInfo
+		allowed       bool
+		wantErr       bool
+	}{
+		{
+			name:          "good",
+			commentsReply: `[{"body": "/ok-to-test", "user": {"login": "owner"}}]`,
+			runinfo: &webvcs.RunInfo{
+				Owner:  "owner",
+				Sender: "nonowner",
+			},
+			allowed: true,
+			wantErr: false,
+		},
+		{
+			name:          "no-ok-to-test",
+			commentsReply: `[{"body": "Foo Bar", "user": {"login": "owner"}}]`,
+			runinfo: &webvcs.RunInfo{
+				Owner:  "owner",
+				Sender: "nonowner",
+			},
+			allowed: false,
+			wantErr: false,
+		},
+		{
+			name:          "ok-to-test-not-from-owner",
+			commentsReply: `[{"body": "/ok-to-test", "user": {"login": "notowner"}}]`,
+			runinfo: &webvcs.RunInfo{
+				Owner:  "owner",
+				Sender: "nonowner",
+			},
+			allowed: false,
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			repoOwnerURL := "http://url.com/owner/repo/1"
+			tt.runinfo.Event = &github.IssueCommentEvent{
+				Issue: &github.Issue{
+					PullRequestLinks: &github.PullRequestLinks{
+						HTMLURL: &repoOwnerURL,
+					},
+				},
+			}
+			tt.runinfo.TriggerTarget = "ok-to-test-comment"
+			fakeclient, mux, _, teardown := ghtesthelper.SetupGH()
+			defer teardown()
+			mux.HandleFunc("/repos/owner/issues/1/comments", func(rw http.ResponseWriter, r *http.Request) {
+				fmt.Fprint(rw, tt.commentsReply)
+			})
+			ctx, _ := rtesting.SetupFakeContext(t)
+			cs := &cli.Clients{
+				GithubClient: webvcs.GithubVCS{
+					Client: fakeclient,
+				},
+			}
+			got, err := aclCheck(ctx, cs, tt.runinfo)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("aclCheck() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.allowed {
+				t.Errorf("aclCheck() = %v, want %v", got, tt.allowed)
+			}
+		})
+	}
+}
+
 func TestAclCheck(t *testing.T) {
 	fakeclient, mux, _, teardown := ghtesthelper.SetupGH()
 	defer teardown()
diff --git a/pkg/webvcs/github_test.go b/pkg/webvcs/github_test.go
index 59117901..74f0c819 100644
--- a/pkg/webvcs/github_test.go
+++ b/pkg/webvcs/github_test.go
@@ -124,7 +124,7 @@ func TestPayLoadFix(t *testing.T) {
 	ctx, _ := rtesting.SetupFakeContext(t)
 	gvcs := NewGithubVCS("none")
 	logger, _ := getLogger()
-	_, err = gvcs.ParsePayload(ctx, logger, "pull_request", payloadFix(string(b)))
+	_, err = gvcs.ParsePayload(ctx, logger, "pull_request", "pull_request", string(b))
 	// would bomb out on "assertion failed: error is not nil: invalid character
 	// '\n' in string literal" if we don't fix the payload
 	assert.NilError(t, err)
@@ -150,7 +150,8 @@ func TestParsePayloadRerequest(t *testing.T) {
 		Client: fakeclient,
 	}
 	logger, observer := getLogger()
-	runinfo, err := gvcs.ParsePayload(ctx, logger, "check_run", checkrunEvent)
+	// TODO
+	runinfo, err := gvcs.ParsePayload(ctx, logger, "check_run", "issue-recheck", checkrunEvent)
 	assert.NilError(t, err)
 
 	assert.Equal(t, prOwner, runinfo.Owner)
@@ -196,7 +197,8 @@ func TestParsePayLoadRetest(t *testing.T) {
 		Client: fakeclient,
 	}
 
-	runinfo, err := gvcs.ParsePayload(ctx, logger, "issue_comment", issueEvent)
+	// TODO
+	runinfo, err := gvcs.ParsePayload(ctx, logger, "issue_comment", "issue_comment", issueEvent)
 	assert.NilError(t, err)
 	assert.Equal(t, prOwner, runinfo.Owner)
 	// Make sure the PR owner is the runinfo.Owner and not the issueSender
@@ -214,7 +216,7 @@ func TestParsePayload(t *testing.T) {
 	gvcs := NewGithubVCS("none")
 	logger, _ := getLogger()
 
-	runinfo, err := gvcs.ParsePayload(ctx, logger, "pull_request", string(b))
+	runinfo, err := gvcs.ParsePayload(ctx, logger, "pull_request", "pull_request", string(b))
 	assert.NilError(t, err)
 	assert.Assert(t, runinfo.BaseBranch == "master")
 	assert.Assert(t, runinfo.Owner == "chmouel")
@@ -227,7 +229,7 @@ func TestParsePayloadInvalid(t *testing.T) {
 	ctx, _ := rtesting.SetupFakeContext(t)
 	gvcs := NewGithubVCS("none")
 	logger, _ := getLogger()
-	_, err := gvcs.ParsePayload(ctx, logger, "pull_request", "hello moto")
+	_, err := gvcs.ParsePayload(ctx, logger, "pull_request", "pull_request", "hello moto")
 	assert.ErrorContains(t, err, "invalid character")
 }
 
@@ -235,7 +237,7 @@ func TestParsePayloadUnkownEvent(t *testing.T) {
 	ctx, _ := rtesting.SetupFakeContext(t)
 	gvcs := NewGithubVCS("none")
 	logger, _ := getLogger()
-	_, err := gvcs.ParsePayload(ctx, logger, "foo", "{\"hello\": \"moto\"}")
+	_, err := gvcs.ParsePayload(ctx, logger, "foo", "foo", "{\"hello\": \"moto\"}")
 	assert.ErrorContains(t, err, "unknown X-Github-Event")
 }
 
@@ -243,7 +245,7 @@ func TestParsePayCannotParse(t *testing.T) {
 	gvcs := NewGithubVCS("none")
 	ctx, _ := rtesting.SetupFakeContext(t)
 	logger, _ := getLogger()
-	_, err := gvcs.ParsePayload(ctx, logger, "gollum", "{}")
+	_, err := gvcs.ParsePayload(ctx, logger, "gollum", "gollum", "{}")
 	assert.Error(t, err, "this event is not supported")
 }
 
@@ -489,6 +491,7 @@ func TestRunInfoCheck(t *testing.T) {
 				CheckRunID:    tt.fields.CheckRunID,
 				Sender:        tt.fields.Sender,
 				EventType:     "pull_request",
+				TriggerTarget: "pull_request",
 			}
 			if err := r.Check(); (err != nil) != tt.wantErr {
 				t.Errorf("RunInfo.Check() error = %v, wantErr %v", err, tt.wantErr)
diff --git a/test/fixtures/issue_comment.json b/test/fixtures/issue_comment-retest.json
similarity index 100%
rename from test/fixtures/issue_comment.json
rename to test/fixtures/issue_comment-retest.json
diff --git a/test/fixtures/issue_comment_ok-to-test.json b/test/fixtures/issue_comment_ok-to-test.json
new file mode 100644
index 00000000..615ed327
--- /dev/null
+++ b/test/fixtures/issue_comment_ok-to-test.json
@@ -0,0 +1,215 @@
+{
+  "action": "created",
+  "issue": {
+    "url": "https://api.github.com/repos/chmouel/scratchmyback/issues/1",
+    "repository_url": "https://api.github.com/repos/chmouel/scratchmyback",
+    "labels_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/1/labels{/name}",
+    "comments_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/1/comments",
+    "events_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/1/events",
+    "html_url": "https://github.com/chmouel/scratchmyback/pull/1",
+    "id": 860639914,
+    "node_id": "MDExOlB1bGxSZXF1ZXN0NjE3NDY3NjE2",
+    "number": 1,
+    "title": "Add tekton ci",
+    "user": {
+      "login": "chmouel",
+      "id": 98980,
+      "node_id": "MDQ6VXNlcjk4OTgw",
+      "avatar_url": "https://avatars.githubusercontent.com/u/98980?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/chmouel",
+      "html_url": "https://github.com/chmouel",
+      "followers_url": "https://api.github.com/users/chmouel/followers",
+      "following_url": "https://api.github.com/users/chmouel/following{/other_user}",
+      "gists_url": "https://api.github.com/users/chmouel/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/chmouel/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/chmouel/subscriptions",
+      "organizations_url": "https://api.github.com/users/chmouel/orgs",
+      "repos_url": "https://api.github.com/users/chmouel/repos",
+      "events_url": "https://api.github.com/users/chmouel/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/chmouel/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "labels": [
+
+    ],
+    "state": "open",
+    "locked": false,
+    "assignee": null,
+    "assignees": [
+
+    ],
+    "milestone": null,
+    "comments": 7,
+    "created_at": "2021-04-18T11:26:57Z",
+    "updated_at": "2021-05-21T13:09:45Z",
+    "closed_at": null,
+    "author_association": "OWNER",
+    "active_lock_reason": null,
+    "pull_request": {
+      "url": "https://api.github.com/repos/chmouel/scratchmyback/pulls/1",
+      "html_url": "https://github.com/chmouel/scratchmyback/pull/1",
+      "diff_url": "https://github.com/chmouel/scratchmyback/pull/1.diff",
+      "patch_url": "https://github.com/chmouel/scratchmyback/pull/1.patch"
+    },
+    "body": "Signed-off-by: Chmouel Boudjnah <chmouel@chmouel.com>",
+    "performed_via_github_app": null
+  },
+  "comment": {
+    "url": "https://api.github.com/repos/chmouel/scratchmyback/issues/comments/845939110",
+    "html_url": "https://github.com/chmouel/scratchmyback/pull/1#issuecomment-845939110",
+    "issue_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/1",
+    "id": 845939110,
+    "node_id": "MDEyOklzc3VlQ29tbWVudDg0NTkzOTExMA==",
+    "user": {
+      "login": "chmouel",
+      "id": 98980,
+      "node_id": "MDQ6VXNlcjk4OTgw",
+      "avatar_url": "https://avatars.githubusercontent.com/u/98980?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/chmouel",
+      "html_url": "https://github.com/chmouel",
+      "followers_url": "https://api.github.com/users/chmouel/followers",
+      "following_url": "https://api.github.com/users/chmouel/following{/other_user}",
+      "gists_url": "https://api.github.com/users/chmouel/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/chmouel/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/chmouel/subscriptions",
+      "organizations_url": "https://api.github.com/users/chmouel/orgs",
+      "repos_url": "https://api.github.com/users/chmouel/repos",
+      "events_url": "https://api.github.com/users/chmouel/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/chmouel/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "created_at": "2021-05-21T13:09:45Z",
+    "updated_at": "2021-05-21T13:09:45Z",
+    "author_association": "OWNER",
+    "body": "A comment with some quote \"foo\":'bar':\"\"\"\"\"\"\"\"/'aA\"a'x'x'''\"'\r\n\r\n/ok-to-test\r\n\r\nBar",
+    "performed_via_github_app": null
+  },
+  "repository": {
+    "id": 359117785,
+    "node_id": "MDEwOlJlcG9zaXRvcnkzNTkxMTc3ODU=",
+    "name": "scratchmyback",
+    "full_name": "chmouel/scratchmyback",
+    "private": false,
+    "owner": {
+      "login": "chmouel",
+      "id": 98980,
+      "node_id": "MDQ6VXNlcjk4OTgw",
+      "avatar_url": "https://avatars.githubusercontent.com/u/98980?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/chmouel",
+      "html_url": "https://github.com/chmouel",
+      "followers_url": "https://api.github.com/users/chmouel/followers",
+      "following_url": "https://api.github.com/users/chmouel/following{/other_user}",
+      "gists_url": "https://api.github.com/users/chmouel/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/chmouel/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/chmouel/subscriptions",
+      "organizations_url": "https://api.github.com/users/chmouel/orgs",
+      "repos_url": "https://api.github.com/users/chmouel/repos",
+      "events_url": "https://api.github.com/users/chmouel/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/chmouel/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "html_url": "https://github.com/chmouel/scratchmyback",
+    "description": "Back scratching",
+    "fork": false,
+    "url": "https://api.github.com/repos/chmouel/scratchmyback",
+    "forks_url": "https://api.github.com/repos/chmouel/scratchmyback/forks",
+    "keys_url": "https://api.github.com/repos/chmouel/scratchmyback/keys{/key_id}",
+    "collaborators_url": "https://api.github.com/repos/chmouel/scratchmyback/collaborators{/collaborator}",
+    "teams_url": "https://api.github.com/repos/chmouel/scratchmyback/teams",
+    "hooks_url": "https://api.github.com/repos/chmouel/scratchmyback/hooks",
+    "issue_events_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/events{/number}",
+    "events_url": "https://api.github.com/repos/chmouel/scratchmyback/events",
+    "assignees_url": "https://api.github.com/repos/chmouel/scratchmyback/assignees{/user}",
+    "branches_url": "https://api.github.com/repos/chmouel/scratchmyback/branches{/branch}",
+    "tags_url": "https://api.github.com/repos/chmouel/scratchmyback/tags",
+    "blobs_url": "https://api.github.com/repos/chmouel/scratchmyback/git/blobs{/sha}",
+    "git_tags_url": "https://api.github.com/repos/chmouel/scratchmyback/git/tags{/sha}",
+    "git_refs_url": "https://api.github.com/repos/chmouel/scratchmyback/git/refs{/sha}",
+    "trees_url": "https://api.github.com/repos/chmouel/scratchmyback/git/trees{/sha}",
+    "statuses_url": "https://api.github.com/repos/chmouel/scratchmyback/statuses/{sha}",
+    "languages_url": "https://api.github.com/repos/chmouel/scratchmyback/languages",
+    "stargazers_url": "https://api.github.com/repos/chmouel/scratchmyback/stargazers",
+    "contributors_url": "https://api.github.com/repos/chmouel/scratchmyback/contributors",
+    "subscribers_url": "https://api.github.com/repos/chmouel/scratchmyback/subscribers",
+    "subscription_url": "https://api.github.com/repos/chmouel/scratchmyback/subscription",
+    "commits_url": "https://api.github.com/repos/chmouel/scratchmyback/commits{/sha}",
+    "git_commits_url": "https://api.github.com/repos/chmouel/scratchmyback/git/commits{/sha}",
+    "comments_url": "https://api.github.com/repos/chmouel/scratchmyback/comments{/number}",
+    "issue_comment_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/comments{/number}",
+    "contents_url": "https://api.github.com/repos/chmouel/scratchmyback/contents/{+path}",
+    "compare_url": "https://api.github.com/repos/chmouel/scratchmyback/compare/{base}...{head}",
+    "merges_url": "https://api.github.com/repos/chmouel/scratchmyback/merges",
+    "archive_url": "https://api.github.com/repos/chmouel/scratchmyback/{archive_format}{/ref}",
+    "downloads_url": "https://api.github.com/repos/chmouel/scratchmyback/downloads",
+    "issues_url": "https://api.github.com/repos/chmouel/scratchmyback/issues{/number}",
+    "pulls_url": "https://api.github.com/repos/chmouel/scratchmyback/pulls{/number}",
+    "milestones_url": "https://api.github.com/repos/chmouel/scratchmyback/milestones{/number}",
+    "notifications_url": "https://api.github.com/repos/chmouel/scratchmyback/notifications{?since,all,participating}",
+    "labels_url": "https://api.github.com/repos/chmouel/scratchmyback/labels{/name}",
+    "releases_url": "https://api.github.com/repos/chmouel/scratchmyback/releases{/id}",
+    "deployments_url": "https://api.github.com/repos/chmouel/scratchmyback/deployments",
+    "created_at": "2021-04-18T10:57:45Z",
+    "updated_at": "2021-05-18T15:22:38Z",
+    "pushed_at": "2021-05-18T15:59:30Z",
+    "git_url": "git://github.com/chmouel/scratchmyback.git",
+    "ssh_url": "git@github.com:chmouel/scratchmyback.git",
+    "clone_url": "https://github.com/chmouel/scratchmyback.git",
+    "svn_url": "https://github.com/chmouel/scratchmyback",
+    "homepage": null,
+    "size": 26,
+    "stargazers_count": 0,
+    "watchers_count": 0,
+    "language": null,
+    "has_issues": true,
+    "has_projects": true,
+    "has_downloads": true,
+    "has_wiki": true,
+    "has_pages": false,
+    "forks_count": 0,
+    "mirror_url": null,
+    "archived": false,
+    "disabled": false,
+    "open_issues_count": 1,
+    "license": {
+      "key": "gpl-3.0",
+      "name": "GNU General Public License v3.0",
+      "spdx_id": "GPL-3.0",
+      "url": "https://api.github.com/licenses/gpl-3.0",
+      "node_id": "MDc6TGljZW5zZTk="
+    },
+    "forks": 0,
+    "open_issues": 1,
+    "watchers": 0,
+    "default_branch": "main"
+  },
+  "sender": {
+    "login": "chmouel",
+    "id": 98980,
+    "node_id": "MDQ6VXNlcjk4OTgw",
+    "avatar_url": "https://avatars.githubusercontent.com/u/98980?v=4",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/chmouel",
+    "html_url": "https://github.com/chmouel",
+    "followers_url": "https://api.github.com/users/chmouel/followers",
+    "following_url": "https://api.github.com/users/chmouel/following{/other_user}",
+    "gists_url": "https://api.github.com/users/chmouel/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/chmouel/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/chmouel/subscriptions",
+    "organizations_url": "https://api.github.com/users/chmouel/orgs",
+    "repos_url": "https://api.github.com/users/chmouel/repos",
+    "events_url": "https://api.github.com/users/chmouel/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/chmouel/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "installation": {
+    "id": 16556382,
+    "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY1NTYzODI="
+  }
+}
diff --git a/test/fixtures/pr-as-non-owner.json b/test/fixtures/pr-as-non-owner.json
new file mode 100644
index 00000000..c3e3994e
--- /dev/null
+++ b/test/fixtures/pr-as-non-owner.json
@@ -0,0 +1,215 @@
+{
+  "action": "created",
+  "issue": {
+    "url": "https://api.github.com/repos/chmouel/scratchmyback/issues/2",
+    "repository_url": "https://api.github.com/repos/chmouel/scratchmyback",
+    "labels_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/2/labels{/name}",
+    "comments_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/2/comments",
+    "events_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/2/events",
+    "html_url": "https://github.com/chmouel/scratchmyback/pull/2",
+    "id": 898033897,
+    "node_id": "MDExOlB1bGxSZXF1ZXN0NjQ5OTU2MDUw",
+    "number": 2,
+    "title": "SamChmou HAHHA what a JOKE BUDDY",
+    "user": {
+      "login": "samchmou",
+      "id": 31999903,
+      "node_id": "MDQ6VXNlcjMxOTk5OTAz",
+      "avatar_url": "https://avatars.githubusercontent.com/u/31999903?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/samchmou",
+      "html_url": "https://github.com/samchmou",
+      "followers_url": "https://api.github.com/users/samchmou/followers",
+      "following_url": "https://api.github.com/users/samchmou/following{/other_user}",
+      "gists_url": "https://api.github.com/users/samchmou/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/samchmou/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/samchmou/subscriptions",
+      "organizations_url": "https://api.github.com/users/samchmou/orgs",
+      "repos_url": "https://api.github.com/users/samchmou/repos",
+      "events_url": "https://api.github.com/users/samchmou/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/samchmou/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "labels": [
+
+    ],
+    "state": "open",
+    "locked": false,
+    "assignee": null,
+    "assignees": [
+
+    ],
+    "milestone": null,
+    "comments": 4,
+    "created_at": "2021-05-21T13:15:36Z",
+    "updated_at": "2021-05-21T13:34:04Z",
+    "closed_at": null,
+    "author_association": "NONE",
+    "active_lock_reason": null,
+    "pull_request": {
+      "url": "https://api.github.com/repos/chmouel/scratchmyback/pulls/2",
+      "html_url": "https://github.com/chmouel/scratchmyback/pull/2",
+      "diff_url": "https://github.com/chmouel/scratchmyback/pull/2.diff",
+      "patch_url": "https://github.com/chmouel/scratchmyback/pull/2.patch"
+    },
+    "body": "",
+    "performed_via_github_app": null
+  },
+  "comment": {
+    "url": "https://api.github.com/repos/chmouel/scratchmyback/issues/comments/845953944",
+    "html_url": "https://github.com/chmouel/scratchmyback/pull/2#issuecomment-845953944",
+    "issue_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/2",
+    "id": 845953944,
+    "node_id": "MDEyOklzc3VlQ29tbWVudDg0NTk1Mzk0NA==",
+    "user": {
+      "login": "chmouel",
+      "id": 98980,
+      "node_id": "MDQ6VXNlcjk4OTgw",
+      "avatar_url": "https://avatars.githubusercontent.com/u/98980?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/chmouel",
+      "html_url": "https://github.com/chmouel",
+      "followers_url": "https://api.github.com/users/chmouel/followers",
+      "following_url": "https://api.github.com/users/chmouel/following{/other_user}",
+      "gists_url": "https://api.github.com/users/chmouel/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/chmouel/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/chmouel/subscriptions",
+      "organizations_url": "https://api.github.com/users/chmouel/orgs",
+      "repos_url": "https://api.github.com/users/chmouel/repos",
+      "events_url": "https://api.github.com/users/chmouel/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/chmouel/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "created_at": "2021-05-21T13:34:04Z",
+    "updated_at": "2021-05-21T13:34:04Z",
+    "author_association": "OWNER",
+    "body": "/ok-to-test",
+    "performed_via_github_app": null
+  },
+  "repository": {
+    "id": 359117785,
+    "node_id": "MDEwOlJlcG9zaXRvcnkzNTkxMTc3ODU=",
+    "name": "scratchmyback",
+    "full_name": "chmouel/scratchmyback",
+    "private": false,
+    "owner": {
+      "login": "chmouel",
+      "id": 98980,
+      "node_id": "MDQ6VXNlcjk4OTgw",
+      "avatar_url": "https://avatars.githubusercontent.com/u/98980?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/chmouel",
+      "html_url": "https://github.com/chmouel",
+      "followers_url": "https://api.github.com/users/chmouel/followers",
+      "following_url": "https://api.github.com/users/chmouel/following{/other_user}",
+      "gists_url": "https://api.github.com/users/chmouel/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/chmouel/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/chmouel/subscriptions",
+      "organizations_url": "https://api.github.com/users/chmouel/orgs",
+      "repos_url": "https://api.github.com/users/chmouel/repos",
+      "events_url": "https://api.github.com/users/chmouel/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/chmouel/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "html_url": "https://github.com/chmouel/scratchmyback",
+    "description": "Back scratching",
+    "fork": false,
+    "url": "https://api.github.com/repos/chmouel/scratchmyback",
+    "forks_url": "https://api.github.com/repos/chmouel/scratchmyback/forks",
+    "keys_url": "https://api.github.com/repos/chmouel/scratchmyback/keys{/key_id}",
+    "collaborators_url": "https://api.github.com/repos/chmouel/scratchmyback/collaborators{/collaborator}",
+    "teams_url": "https://api.github.com/repos/chmouel/scratchmyback/teams",
+    "hooks_url": "https://api.github.com/repos/chmouel/scratchmyback/hooks",
+    "issue_events_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/events{/number}",
+    "events_url": "https://api.github.com/repos/chmouel/scratchmyback/events",
+    "assignees_url": "https://api.github.com/repos/chmouel/scratchmyback/assignees{/user}",
+    "branches_url": "https://api.github.com/repos/chmouel/scratchmyback/branches{/branch}",
+    "tags_url": "https://api.github.com/repos/chmouel/scratchmyback/tags",
+    "blobs_url": "https://api.github.com/repos/chmouel/scratchmyback/git/blobs{/sha}",
+    "git_tags_url": "https://api.github.com/repos/chmouel/scratchmyback/git/tags{/sha}",
+    "git_refs_url": "https://api.github.com/repos/chmouel/scratchmyback/git/refs{/sha}",
+    "trees_url": "https://api.github.com/repos/chmouel/scratchmyback/git/trees{/sha}",
+    "statuses_url": "https://api.github.com/repos/chmouel/scratchmyback/statuses/{sha}",
+    "languages_url": "https://api.github.com/repos/chmouel/scratchmyback/languages",
+    "stargazers_url": "https://api.github.com/repos/chmouel/scratchmyback/stargazers",
+    "contributors_url": "https://api.github.com/repos/chmouel/scratchmyback/contributors",
+    "subscribers_url": "https://api.github.com/repos/chmouel/scratchmyback/subscribers",
+    "subscription_url": "https://api.github.com/repos/chmouel/scratchmyback/subscription",
+    "commits_url": "https://api.github.com/repos/chmouel/scratchmyback/commits{/sha}",
+    "git_commits_url": "https://api.github.com/repos/chmouel/scratchmyback/git/commits{/sha}",
+    "comments_url": "https://api.github.com/repos/chmouel/scratchmyback/comments{/number}",
+    "issue_comment_url": "https://api.github.com/repos/chmouel/scratchmyback/issues/comments{/number}",
+    "contents_url": "https://api.github.com/repos/chmouel/scratchmyback/contents/{+path}",
+    "compare_url": "https://api.github.com/repos/chmouel/scratchmyback/compare/{base}...{head}",
+    "merges_url": "https://api.github.com/repos/chmouel/scratchmyback/merges",
+    "archive_url": "https://api.github.com/repos/chmouel/scratchmyback/{archive_format}{/ref}",
+    "downloads_url": "https://api.github.com/repos/chmouel/scratchmyback/downloads",
+    "issues_url": "https://api.github.com/repos/chmouel/scratchmyback/issues{/number}",
+    "pulls_url": "https://api.github.com/repos/chmouel/scratchmyback/pulls{/number}",
+    "milestones_url": "https://api.github.com/repos/chmouel/scratchmyback/milestones{/number}",
+    "notifications_url": "https://api.github.com/repos/chmouel/scratchmyback/notifications{?since,all,participating}",
+    "labels_url": "https://api.github.com/repos/chmouel/scratchmyback/labels{/name}",
+    "releases_url": "https://api.github.com/repos/chmouel/scratchmyback/releases{/id}",
+    "deployments_url": "https://api.github.com/repos/chmouel/scratchmyback/deployments",
+    "created_at": "2021-04-18T10:57:45Z",
+    "updated_at": "2021-05-18T15:22:38Z",
+    "pushed_at": "2021-05-21T13:15:36Z",
+    "git_url": "git://github.com/chmouel/scratchmyback.git",
+    "ssh_url": "git@github.com:chmouel/scratchmyback.git",
+    "clone_url": "https://github.com/chmouel/scratchmyback.git",
+    "svn_url": "https://github.com/chmouel/scratchmyback",
+    "homepage": null,
+    "size": 26,
+    "stargazers_count": 0,
+    "watchers_count": 0,
+    "language": null,
+    "has_issues": true,
+    "has_projects": true,
+    "has_downloads": true,
+    "has_wiki": true,
+    "has_pages": false,
+    "forks_count": 1,
+    "mirror_url": null,
+    "archived": false,
+    "disabled": false,
+    "open_issues_count": 2,
+    "license": {
+      "key": "gpl-3.0",
+      "name": "GNU General Public License v3.0",
+      "spdx_id": "GPL-3.0",
+      "url": "https://api.github.com/licenses/gpl-3.0",
+      "node_id": "MDc6TGljZW5zZTk="
+    },
+    "forks": 1,
+    "open_issues": 2,
+    "watchers": 0,
+    "default_branch": "main"
+  },
+  "sender": {
+    "login": "chmouel",
+    "id": 98980,
+    "node_id": "MDQ6VXNlcjk4OTgw",
+    "avatar_url": "https://avatars.githubusercontent.com/u/98980?v=4",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/chmouel",
+    "html_url": "https://github.com/chmouel",
+    "followers_url": "https://api.github.com/users/chmouel/followers",
+    "following_url": "https://api.github.com/users/chmouel/following{/other_user}",
+    "gists_url": "https://api.github.com/users/chmouel/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/chmouel/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/chmouel/subscriptions",
+    "organizations_url": "https://api.github.com/users/chmouel/orgs",
+    "repos_url": "https://api.github.com/users/chmouel/repos",
+    "events_url": "https://api.github.com/users/chmouel/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/chmouel/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "installation": {
+    "id": 16556382,
+    "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY1NTYzODI="
+  }
+}