Repository: openshift-pipelines/pipelines-as-code
Pull request #52 (2021-05-10 14:24:44)

Support OWNERS file at root repo
If we find a OWNERS file at the repository root in the main
branch we parse it in prow syntax and allow the PR to be tested if the
owners is in there.
    
Doesn't handle OWNERS_ALIAS (yet or not yet)
    
Signed-off-by: Chmouel Boudjnah <chmouel@redhat.com>



Patch:
diff --git a/README.md b/README.md
index 684cc705..a8b86653 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,14 @@ User create a Pull Request.
 
 If the user sending the Pull Request is not the owner of the repository or not a public member of the organization where the repository belong to, `Pipeline as Code` will not run.
 
+If the user sending the Pull Request is inside an OWNER file located in the repository root in the main branch (the main branch as defined in the Github configuration for the repo) in the `approvers` or `reviewers` section like this :  
+
+```yaml
+approvers:
+  - approved
+```
+then the user `approved` will be allowed.
+
 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/pkg/pipelineascode/acl.go b/pkg/pipelineascode/acl.go
index 6fca52dd..57192ad1 100644
--- a/pkg/pipelineascode/acl.go
+++ b/pkg/pipelineascode/acl.go
@@ -1,10 +1,19 @@
 package pipelineascode
 
 import (
+	"strings"
+
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/webvcs"
+	"sigs.k8s.io/yaml"
 )
 
+// 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
 func aclCheck(cs *cli.Clients, runinfo *webvcs.RunInfo) (bool, error) {
 	// If the user who submitted the PR is the same as the Owner of the repo
@@ -24,6 +33,25 @@ func aclCheck(cs *cli.Clients, runinfo *webvcs.RunInfo) (bool, error) {
 		return true, nil
 	}
 
-	// TODO: there is going to be more stuff in there
-	return false, err
+	// If we have a prow OWNERS file in the defaultBranch (ie: master) then
+	// parse it in approvers and reviewers field and check if sender is in there.
+	ownerFile, err := cs.GithubClient.GetFileFromDefaultBranch("OWNERS", runinfo)
+
+	// Don't error out if the OWNERS file cannot be found
+	if err != nil && !strings.Contains(err.Error(), "cannot find") {
+		return false, err
+	} else if ownerFile != "" {
+		var ownerConfig OwnersConfig
+		err := yaml.Unmarshal([]byte(ownerFile), &ownerConfig)
+		if err != nil {
+			return false, err
+		}
+		for _, owner := range append(ownerConfig.Approvers, ownerConfig.Reviewers...) {
+			if owner == runinfo.Sender {
+				return true, nil
+			}
+		}
+	}
+
+	return false, nil
 }
diff --git a/pkg/webvcs/github.go b/pkg/webvcs/github.go
index 0d7d6315..73365dda 100644
--- a/pkg/webvcs/github.go
+++ b/pkg/webvcs/github.go
@@ -134,10 +134,16 @@ func (v GithubVCS) ParsePayload(log *zap.SugaredLogger, payload string) (*RunInf
 // CheckSenderOrgMembership Get sender user's organization. We can
 // only get the one that the user sets as public 🤷
 func (v GithubVCS) CheckSenderOrgMembership(runinfo *RunInfo) (bool, error) {
-	users, _, err := v.Client.Organizations.ListMembers(v.Context, runinfo.Owner,
+	users, resp, err := v.Client.Organizations.ListMembers(v.Context, runinfo.Owner,
 		&github.ListMembersOptions{
 			PublicOnly: true, // We can't list private member in a org
 		})
+
+	// If we are 404 it means we are checking a repo owner and not a org so let's bail out with grace
+	if resp.Response.StatusCode == http.StatusNotFound {
+		return false, nil
+	}
+
 	if err != nil {
 		return false, err
 	}


Test patch:
diff --git a/pkg/cmd/pipelineascode/pipelineascode_test.go b/pkg/cmd/pipelineascode/pipelineascode_test.go
index 5ebe9336..b94c53a7 100644
--- a/pkg/cmd/pipelineascode/pipelineascode_test.go
+++ b/pkg/cmd/pipelineascode/pipelineascode_test.go
@@ -4,10 +4,8 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"os"
 	"testing"
 
-	"github.com/davecgh/go-spew/spew"
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
 	pacpkg "github.com/openshift-pipelines/pipelines-as-code/pkg/pipelineascode"
 	testclient "github.com/openshift-pipelines/pipelines-as-code/pkg/test/clients"
@@ -29,13 +27,6 @@ func TestRunWrapOld(t *testing.T) {
 	checkid := 1234
 	defer teardown()
 
-	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		fmt.Fprintf(os.Stderr, `
-		============================================
-		%s
-		============================================
-		`, spew.Sdump(r.URL))
-	})
 	mux.HandleFunc("/repos/chmouel/scratchmyback/check-runs", func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, `{"id": %d}`, checkid)
 	})
diff --git a/pkg/pipelineascode/acl_test.go b/pkg/pipelineascode/acl_test.go
index 31de985f..15bc610f 100644
--- a/pkg/pipelineascode/acl_test.go
+++ b/pkg/pipelineascode/acl_test.go
@@ -2,6 +2,7 @@ package pipelineascode
 
 import (
 	"context"
+	"encoding/base64"
 	"fmt"
 	"net/http"
 	"testing"
@@ -17,6 +18,8 @@ func TestAclCheck(t *testing.T) {
 
 	orgallowed := "allowed"
 	orgdenied := "denied"
+	repoOwnerFileAllowed := "repoOwnerAllowed"
+
 	errit := "err"
 
 	mux.HandleFunc("/orgs/"+orgallowed+"/public_members", func(rw http.ResponseWriter, r *http.Request) {
@@ -26,10 +29,23 @@ func TestAclCheck(t *testing.T) {
 	mux.HandleFunc("/orgs/"+orgdenied+"/public_members", func(rw http.ResponseWriter, r *http.Request) {
 		fmt.Fprint(rw, `[]`)
 	})
+
 	mux.HandleFunc("/orgs/"+errit+"/public_members", func(rw http.ResponseWriter, r *http.Request) {
 		fmt.Fprint(rw, `x1x`)
 	})
 
+	mux.HandleFunc("/orgs/"+repoOwnerFileAllowed+"/public_members", func(rw http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(rw, `[]`)
+	})
+
+	mux.HandleFunc("/repos/"+repoOwnerFileAllowed+"/contents/OWNERS", func(rw http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(rw, `{"name": "OWNERS", "path": "OWNERS", "sha": "ownerssha"}`)
+	})
+
+	mux.HandleFunc("/repos/"+repoOwnerFileAllowed+"/git/blobs/ownerssha", func(rw http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(rw, `{"content": "%s"}`, base64.RawStdEncoding.EncodeToString([]byte("approvers:\n  - approved\n")))
+	})
+
 	gvcs := webvcs.GithubVCS{
 		Client:  fakeclient,
 		Context: context.Background(),
@@ -50,6 +66,15 @@ func TestAclCheck(t *testing.T) {
 			allowed: true,
 			wantErr: false,
 		},
+		{
+			name: "sender allowed from owner file",
+			runinfo: &webvcs.RunInfo{
+				Owner:  repoOwnerFileAllowed,
+				Sender: "approved",
+			},
+			allowed: true,
+			wantErr: false,
+		},
 		{
 			name: "owner is sender is allowed",
 			runinfo: &webvcs.RunInfo{