Repository: openshift-pipelines/pipelines-as-code
Pull request #14 (2021-04-29 11:23:05)

Make repository namespaced and make sure there is no hijackness going 👮
Convert cluster scoped CRD to Namespaced

Make sure the CR repo target namespace is the namespace where the CR is installed.

Additionally grab the tekton.yaml from the DefaultBranch and check if there is
a namespace directive in there. Make sure the CR repo is installed in that namespace.

This should avoid most of the hijacking.


chmouel: @sbose78 @siamaksade if you want to have a quick look at the logic, i don't think i have missed anything and tested multiple scenarios...

Patch:
diff --git a/config/300-repositories.yaml b/config/300-repositories.yaml
index 215ad9fa..b49569c9 100644
--- a/config/300-repositories.yaml
+++ b/config/300-repositories.yaml
@@ -67,7 +67,7 @@ spec:
                   type: string
               type: object
           type: object
-  scope: Cluster
+  scope: Namespaced
   names:
     plural: repositories
     singular: repository
diff --git a/pkg/apis/pipelinesascode/v1alpha1/types.go b/pkg/apis/pipelinesascode/v1alpha1/types.go
index e9f063ef..7f58f580 100644
--- a/pkg/apis/pipelinesascode/v1alpha1/types.go
+++ b/pkg/apis/pipelinesascode/v1alpha1/types.go
@@ -6,7 +6,6 @@ import (
 )
 
 // +genclient
-// +genclient:nonNamespaced
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // Repository is the representation of a repo
diff --git a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_pipelinesascode_client.go b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_pipelinesascode_client.go
index 3a702b61..090c95bb 100644
--- a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_pipelinesascode_client.go
+++ b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_pipelinesascode_client.go
@@ -28,8 +28,8 @@ type FakePipelinesascodeV1alpha1 struct {
 	*testing.Fake
 }
 
-func (c *FakePipelinesascodeV1alpha1) Repositories() v1alpha1.RepositoryInterface {
-	return &FakeRepositories{c}
+func (c *FakePipelinesascodeV1alpha1) Repositories(namespace string) v1alpha1.RepositoryInterface {
+	return &FakeRepositories{c, namespace}
 }
 
 // RESTClient returns a RESTClient that is used to communicate
diff --git a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_repository.go b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_repository.go
index b5b86428..ee4d6269 100644
--- a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_repository.go
+++ b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/fake/fake_repository.go
@@ -33,6 +33,7 @@ import (
 // FakeRepositories implements RepositoryInterface
 type FakeRepositories struct {
 	Fake *FakePipelinesascodeV1alpha1
+	ns   string
 }
 
 var repositoriesResource = schema.GroupVersionResource{Group: "pipelinesascode.tekton.dev", Version: "v1alpha1", Resource: "repositories"}
@@ -42,7 +43,8 @@ var repositoriesKind = schema.GroupVersionKind{Group: "pipelinesascode.tekton.de
 // Get takes name of the repository, and returns the corresponding repository object, and an error if there is any.
 func (c *FakeRepositories) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Repository, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewRootGetAction(repositoriesResource, name), &v1alpha1.Repository{})
+		Invokes(testing.NewGetAction(repositoriesResource, c.ns, name), &v1alpha1.Repository{})
+
 	if obj == nil {
 		return nil, err
 	}
@@ -52,7 +54,8 @@ func (c *FakeRepositories) Get(ctx context.Context, name string, options v1.GetO
 // List takes label and field selectors, and returns the list of Repositories that match those selectors.
 func (c *FakeRepositories) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RepositoryList, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewRootListAction(repositoriesResource, repositoriesKind, opts), &v1alpha1.RepositoryList{})
+		Invokes(testing.NewListAction(repositoriesResource, repositoriesKind, c.ns, opts), &v1alpha1.RepositoryList{})
+
 	if obj == nil {
 		return nil, err
 	}
@@ -73,13 +76,15 @@ func (c *FakeRepositories) List(ctx context.Context, opts v1.ListOptions) (resul
 // Watch returns a watch.Interface that watches the requested repositories.
 func (c *FakeRepositories) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
 	return c.Fake.
-		InvokesWatch(testing.NewRootWatchAction(repositoriesResource, opts))
+		InvokesWatch(testing.NewWatchAction(repositoriesResource, c.ns, opts))
+
 }
 
 // Create takes the representation of a repository and creates it.  Returns the server's representation of the repository, and an error, if there is any.
 func (c *FakeRepositories) Create(ctx context.Context, repository *v1alpha1.Repository, opts v1.CreateOptions) (result *v1alpha1.Repository, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewRootCreateAction(repositoriesResource, repository), &v1alpha1.Repository{})
+		Invokes(testing.NewCreateAction(repositoriesResource, c.ns, repository), &v1alpha1.Repository{})
+
 	if obj == nil {
 		return nil, err
 	}
@@ -89,7 +94,8 @@ func (c *FakeRepositories) Create(ctx context.Context, repository *v1alpha1.Repo
 // Update takes the representation of a repository and updates it. Returns the server's representation of the repository, and an error, if there is any.
 func (c *FakeRepositories) Update(ctx context.Context, repository *v1alpha1.Repository, opts v1.UpdateOptions) (result *v1alpha1.Repository, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewRootUpdateAction(repositoriesResource, repository), &v1alpha1.Repository{})
+		Invokes(testing.NewUpdateAction(repositoriesResource, c.ns, repository), &v1alpha1.Repository{})
+
 	if obj == nil {
 		return nil, err
 	}
@@ -100,7 +106,8 @@ func (c *FakeRepositories) Update(ctx context.Context, repository *v1alpha1.Repo
 // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
 func (c *FakeRepositories) UpdateStatus(ctx context.Context, repository *v1alpha1.Repository, opts v1.UpdateOptions) (*v1alpha1.Repository, error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewRootUpdateSubresourceAction(repositoriesResource, "status", repository), &v1alpha1.Repository{})
+		Invokes(testing.NewUpdateSubresourceAction(repositoriesResource, "status", c.ns, repository), &v1alpha1.Repository{})
+
 	if obj == nil {
 		return nil, err
 	}
@@ -110,13 +117,14 @@ func (c *FakeRepositories) UpdateStatus(ctx context.Context, repository *v1alpha
 // Delete takes name of the repository and deletes it. Returns an error if one occurs.
 func (c *FakeRepositories) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewRootDeleteAction(repositoriesResource, name), &v1alpha1.Repository{})
+		Invokes(testing.NewDeleteAction(repositoriesResource, c.ns, name), &v1alpha1.Repository{})
+
 	return err
 }
 
 // DeleteCollection deletes a collection of objects.
 func (c *FakeRepositories) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
-	action := testing.NewRootDeleteCollectionAction(repositoriesResource, listOpts)
+	action := testing.NewDeleteCollectionAction(repositoriesResource, c.ns, listOpts)
 
 	_, err := c.Fake.Invokes(action, &v1alpha1.RepositoryList{})
 	return err
@@ -125,7 +133,8 @@ func (c *FakeRepositories) DeleteCollection(ctx context.Context, opts v1.DeleteO
 // Patch applies the patch and returns the patched repository.
 func (c *FakeRepositories) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Repository, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewRootPatchSubresourceAction(repositoriesResource, name, pt, data, subresources...), &v1alpha1.Repository{})
+		Invokes(testing.NewPatchSubresourceAction(repositoriesResource, c.ns, name, pt, data, subresources...), &v1alpha1.Repository{})
+
 	if obj == nil {
 		return nil, err
 	}
diff --git a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/pipelinesascode_client.go b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/pipelinesascode_client.go
index e8190bae..cc2848a0 100644
--- a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/pipelinesascode_client.go
+++ b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/pipelinesascode_client.go
@@ -34,8 +34,8 @@ type PipelinesascodeV1alpha1Client struct {
 	restClient rest.Interface
 }
 
-func (c *PipelinesascodeV1alpha1Client) Repositories() RepositoryInterface {
-	return newRepositories(c)
+func (c *PipelinesascodeV1alpha1Client) Repositories(namespace string) RepositoryInterface {
+	return newRepositories(c, namespace)
 }
 
 // NewForConfig creates a new PipelinesascodeV1alpha1Client for the given config.
diff --git a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/repository.go b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/repository.go
index c1ecb0e5..aada2af6 100644
--- a/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/repository.go
+++ b/pkg/generated/clientset/versioned/typed/pipelinesascode/v1alpha1/repository.go
@@ -33,7 +33,7 @@ import (
 // RepositoriesGetter has a method to return a RepositoryInterface.
 // A group's client should implement this interface.
 type RepositoriesGetter interface {
-	Repositories() RepositoryInterface
+	Repositories(namespace string) RepositoryInterface
 }
 
 // RepositoryInterface has methods to work with Repository resources.
@@ -53,12 +53,14 @@ type RepositoryInterface interface {
 // repositories implements RepositoryInterface
 type repositories struct {
 	client rest.Interface
+	ns     string
 }
 
 // newRepositories returns a Repositories
-func newRepositories(c *PipelinesascodeV1alpha1Client) *repositories {
+func newRepositories(c *PipelinesascodeV1alpha1Client, namespace string) *repositories {
 	return &repositories{
 		client: c.RESTClient(),
+		ns:     namespace,
 	}
 }
 
@@ -66,6 +68,7 @@ func newRepositories(c *PipelinesascodeV1alpha1Client) *repositories {
 func (c *repositories) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Repository, err error) {
 	result = &v1alpha1.Repository{}
 	err = c.client.Get().
+		Namespace(c.ns).
 		Resource("repositories").
 		Name(name).
 		VersionedParams(&options, scheme.ParameterCodec).
@@ -82,6 +85,7 @@ func (c *repositories) List(ctx context.Context, opts v1.ListOptions) (result *v
 	}
 	result = &v1alpha1.RepositoryList{}
 	err = c.client.Get().
+		Namespace(c.ns).
 		Resource("repositories").
 		VersionedParams(&opts, scheme.ParameterCodec).
 		Timeout(timeout).
@@ -98,6 +102,7 @@ func (c *repositories) Watch(ctx context.Context, opts v1.ListOptions) (watch.In
 	}
 	opts.Watch = true
 	return c.client.Get().
+		Namespace(c.ns).
 		Resource("repositories").
 		VersionedParams(&opts, scheme.ParameterCodec).
 		Timeout(timeout).
@@ -108,6 +113,7 @@ func (c *repositories) Watch(ctx context.Context, opts v1.ListOptions) (watch.In
 func (c *repositories) Create(ctx context.Context, repository *v1alpha1.Repository, opts v1.CreateOptions) (result *v1alpha1.Repository, err error) {
 	result = &v1alpha1.Repository{}
 	err = c.client.Post().
+		Namespace(c.ns).
 		Resource("repositories").
 		VersionedParams(&opts, scheme.ParameterCodec).
 		Body(repository).
@@ -120,6 +126,7 @@ func (c *repositories) Create(ctx context.Context, repository *v1alpha1.Reposito
 func (c *repositories) Update(ctx context.Context, repository *v1alpha1.Repository, opts v1.UpdateOptions) (result *v1alpha1.Repository, err error) {
 	result = &v1alpha1.Repository{}
 	err = c.client.Put().
+		Namespace(c.ns).
 		Resource("repositories").
 		Name(repository.Name).
 		VersionedParams(&opts, scheme.ParameterCodec).
@@ -134,6 +141,7 @@ func (c *repositories) Update(ctx context.Context, repository *v1alpha1.Reposito
 func (c *repositories) UpdateStatus(ctx context.Context, repository *v1alpha1.Repository, opts v1.UpdateOptions) (result *v1alpha1.Repository, err error) {
 	result = &v1alpha1.Repository{}
 	err = c.client.Put().
+		Namespace(c.ns).
 		Resource("repositories").
 		Name(repository.Name).
 		SubResource("status").
@@ -147,6 +155,7 @@ func (c *repositories) UpdateStatus(ctx context.Context, repository *v1alpha1.Re
 // Delete takes name of the repository and deletes it. Returns an error if one occurs.
 func (c *repositories) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	return c.client.Delete().
+		Namespace(c.ns).
 		Resource("repositories").
 		Name(name).
 		Body(&opts).
@@ -161,6 +170,7 @@ func (c *repositories) DeleteCollection(ctx context.Context, opts v1.DeleteOptio
 		timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
 	}
 	return c.client.Delete().
+		Namespace(c.ns).
 		Resource("repositories").
 		VersionedParams(&listOpts, scheme.ParameterCodec).
 		Timeout(timeout).
@@ -173,6 +183,7 @@ func (c *repositories) DeleteCollection(ctx context.Context, opts v1.DeleteOptio
 func (c *repositories) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Repository, err error) {
 	result = &v1alpha1.Repository{}
 	err = c.client.Patch(pt).
+		Namespace(c.ns).
 		Resource("repositories").
 		Name(name).
 		SubResource(subresources...).
diff --git a/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/interface.go b/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/interface.go
index a57d9295..eda1526d 100644
--- a/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/interface.go
+++ b/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/interface.go
@@ -41,5 +41,5 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
 
 // Repositories returns a RepositoryInformer.
 func (v *version) Repositories() RepositoryInformer {
-	return &repositoryInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
+	return &repositoryInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
 }
diff --git a/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/repository.go b/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/repository.go
index b290e2c9..7f8617d3 100644
--- a/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/repository.go
+++ b/pkg/generated/informers/externalversions/pipelinesascode/v1alpha1/repository.go
@@ -42,32 +42,33 @@ type RepositoryInformer interface {
 type repositoryInformer struct {
 	factory          internalinterfaces.SharedInformerFactory
 	tweakListOptions internalinterfaces.TweakListOptionsFunc
+	namespace        string
 }
 
 // NewRepositoryInformer constructs a new informer for Repository type.
 // Always prefer using an informer factory to get a shared informer instead of getting an independent
 // one. This reduces memory footprint and number of connections to the server.
-func NewRepositoryInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
-	return NewFilteredRepositoryInformer(client, resyncPeriod, indexers, nil)
+func NewRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredRepositoryInformer(client, namespace, resyncPeriod, indexers, nil)
 }
 
 // NewFilteredRepositoryInformer constructs a new informer for Repository type.
 // Always prefer using an informer factory to get a shared informer instead of getting an independent
 // one. This reduces memory footprint and number of connections to the server.
-func NewFilteredRepositoryInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+func NewFilteredRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
 	return cache.NewSharedIndexInformer(
 		&cache.ListWatch{
 			ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
 				if tweakListOptions != nil {
 					tweakListOptions(&options)
 				}
-				return client.PipelinesascodeV1alpha1().Repositories().List(context.TODO(), options)
+				return client.PipelinesascodeV1alpha1().Repositories(namespace).List(context.TODO(), options)
 			},
 			WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
 				if tweakListOptions != nil {
 					tweakListOptions(&options)
 				}
-				return client.PipelinesascodeV1alpha1().Repositories().Watch(context.TODO(), options)
+				return client.PipelinesascodeV1alpha1().Repositories(namespace).Watch(context.TODO(), options)
 			},
 		},
 		&pipelinesascodev1alpha1.Repository{},
@@ -77,7 +78,7 @@ func NewFilteredRepositoryInformer(client versioned.Interface, resyncPeriod time
 }
 
 func (f *repositoryInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
-	return NewFilteredRepositoryInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+	return NewFilteredRepositoryInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
 }
 
 func (f *repositoryInformer) Informer() cache.SharedIndexInformer {
diff --git a/pkg/generated/listers/pipelinesascode/v1alpha1/expansion_generated.go b/pkg/generated/listers/pipelinesascode/v1alpha1/expansion_generated.go
index 0c0183cc..0b5d9ee0 100644
--- a/pkg/generated/listers/pipelinesascode/v1alpha1/expansion_generated.go
+++ b/pkg/generated/listers/pipelinesascode/v1alpha1/expansion_generated.go
@@ -21,3 +21,7 @@ package v1alpha1
 // RepositoryListerExpansion allows custom methods to be added to
 // RepositoryLister.
 type RepositoryListerExpansion interface{}
+
+// RepositoryNamespaceListerExpansion allows custom methods to be added to
+// RepositoryNamespaceLister.
+type RepositoryNamespaceListerExpansion interface{}
diff --git a/pkg/generated/listers/pipelinesascode/v1alpha1/repository.go b/pkg/generated/listers/pipelinesascode/v1alpha1/repository.go
index 99b2a337..a614e32a 100644
--- a/pkg/generated/listers/pipelinesascode/v1alpha1/repository.go
+++ b/pkg/generated/listers/pipelinesascode/v1alpha1/repository.go
@@ -31,9 +31,8 @@ type RepositoryLister interface {
 	// List lists all Repositories in the indexer.
 	// Objects returned here must be treated as read-only.
 	List(selector labels.Selector) (ret []*v1alpha1.Repository, err error)
-	// Get retrieves the Repository from the index for a given name.
-	// Objects returned here must be treated as read-only.
-	Get(name string) (*v1alpha1.Repository, error)
+	// Repositories returns an object that can list and get Repositories.
+	Repositories(namespace string) RepositoryNamespaceLister
 	RepositoryListerExpansion
 }
 
@@ -55,9 +54,41 @@ func (s *repositoryLister) List(selector labels.Selector) (ret []*v1alpha1.Repos
 	return ret, err
 }
 
-// Get retrieves the Repository from the index for a given name.
-func (s *repositoryLister) Get(name string) (*v1alpha1.Repository, error) {
-	obj, exists, err := s.indexer.GetByKey(name)
+// Repositories returns an object that can list and get Repositories.
+func (s *repositoryLister) Repositories(namespace string) RepositoryNamespaceLister {
+	return repositoryNamespaceLister{indexer: s.indexer, namespace: namespace}
+}
+
+// RepositoryNamespaceLister helps list and get Repositories.
+// All objects returned here must be treated as read-only.
+type RepositoryNamespaceLister interface {
+	// List lists all Repositories in the indexer for a given namespace.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*v1alpha1.Repository, err error)
+	// Get retrieves the Repository from the indexer for a given namespace and name.
+	// Objects returned here must be treated as read-only.
+	Get(name string) (*v1alpha1.Repository, error)
+	RepositoryNamespaceListerExpansion
+}
+
+// repositoryNamespaceLister implements the RepositoryNamespaceLister
+// interface.
+type repositoryNamespaceLister struct {
+	indexer   cache.Indexer
+	namespace string
+}
+
+// List lists all Repositories in the indexer for a given namespace.
+func (s repositoryNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Repository, err error) {
+	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1alpha1.Repository))
+	})
+	return ret, err
+}
+
+// Get retrieves the Repository from the indexer for a given namespace and name.
+func (s repositoryNamespaceLister) Get(name string) (*v1alpha1.Repository, error) {
+	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/kubernetes/consoleui.go b/pkg/kubernetes/consoleui.go
index acae81d4..d09a647e 100644
--- a/pkg/kubernetes/consoleui.go
+++ b/pkg/kubernetes/consoleui.go
@@ -29,13 +29,13 @@ func getOpenshiftConsole(cs *cli.Clients, ns, pr string) (string, error) {
 
 	spec, ok := route.Object["spec"].(map[string]interface{})
 	if !ok {
-		// this condition is satisfied if there's no metadata at all in the provided CRD
+		// this condition is satisfied if there's no metadata at all in the provided CR
 		return "", fmt.Errorf("couldn't find \"spec\" in the OpenShift Console route")
 	}
 
 	host, ok := spec["host"].(string)
 	if !ok {
-		// this condition is satisfied if there's no metadata at all in the provided CRD
+		// this condition is satisfied if there's no metadata at all in the provided CR
 		return "", fmt.Errorf("couldn't find \"spec.host\" in the OpenShift Console route")
 	}
 
diff --git a/pkg/pipelineascode/pipelineascode.go b/pkg/pipelineascode/pipelineascode.go
index c03658c2..1c6e00bc 100644
--- a/pkg/pipelineascode/pipelineascode.go
+++ b/pkg/pipelineascode/pipelineascode.go
@@ -2,6 +2,8 @@ package pipelineascode
 
 import (
 	"context"
+	"fmt"
+	"path/filepath"
 
 	apipac "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
@@ -22,16 +24,31 @@ type Options struct {
 	Payload string
 }
 
-func getRepoByCRD(cs *cli.Clients, url, branch, eventType string) (apipac.Repository, error) {
+func getRepoByCR(cs *cli.Clients, url, branch, eventType, forceNamespace string) (apipac.Repository, error) {
 	var repository apipac.Repository
 
-	repositories, err := cs.PipelineAsCode.PipelinesascodeV1alpha1().Repositories().List(
+	repositories, err := cs.PipelineAsCode.PipelinesascodeV1alpha1().Repositories("").List(
 		context.Background(), metav1.ListOptions{})
+
 	if err != nil {
 		return repository, err
 	}
 	for _, value := range repositories.Items {
 		if value.Spec.URL == url && value.Spec.Branch == branch && value.Spec.EventType == eventType {
+			if forceNamespace != "" && value.Namespace != forceNamespace {
+				return repository, fmt.Errorf(
+					"Repo CR matches but should be installed in \"%s\" as configured from tekton.yaml on the main branch",
+					forceNamespace)
+			}
+
+			// Disallow attempts for hijacks. If the installed CR is not configured on the
+			// Namespace the Spec is targeting then disallow it.
+			if value.Namespace != value.Spec.Namespace {
+				return repository, fmt.Errorf("Repo CR %s matches but belongs to \"%s\" while it should be in \"%s\"",
+					value.Name,
+					value.Namespace,
+					value.Spec.Namespace)
+			}
 			return value, nil
 		}
 	}
@@ -40,14 +57,25 @@ func getRepoByCRD(cs *cli.Clients, url, branch, eventType string) (apipac.Reposi
 
 func Run(cs *cli.Clients, runinfo *webvcs.RunInfo) error {
 	var err error
+	var maintekton TektonYamlConfig
+
 	var ctx = context.Background()
+
 	checkRun, err := cs.GithubClient.CreateCheckRun("in_progress", runinfo)
 	if err != nil {
 		return err
 	}
 	runinfo.CheckRunID = checkRun.ID
 
-	repo, err := getRepoByCRD(cs, runinfo.URL, runinfo.Branch, "pull_request")
+	maintektonyaml, _ := cs.GithubClient.GetFileFromDefaultBranch(filepath.Join(tektonDir, tektonConfigurationFile), runinfo)
+	if maintektonyaml != "" {
+		maintekton, err = processTektonYaml(cs, runinfo, maintektonyaml)
+		if err != nil {
+			return err
+		}
+	}
+
+	repo, err := getRepoByCR(cs, runinfo.URL, runinfo.Branch, "pull_request", maintekton.Namespace)
 	if err != nil {
 		return err
 	}
@@ -140,7 +168,7 @@ func Run(cs *cli.Clients, runinfo *webvcs.RunInfo) error {
 		repo.Status = repo.Status[:maxPipelineRunStatusRun-1]
 	}
 	repo.Status = append(repo.Status, repoStatus)
-	nrepo, err := cs.PipelineAsCode.PipelinesascodeV1alpha1().Repositories().Update(
+	nrepo, err := cs.PipelineAsCode.PipelinesascodeV1alpha1().Repositories(repo.Namespace).Update(
 		ctx, &repo, v1.UpdateOptions{})
 	if err != nil {
 		return err
diff --git a/pkg/pipelineascode/processconfig.go b/pkg/pipelineascode/processconfig.go
index ab9a1d7c..9f9a6970 100644
--- a/pkg/pipelineascode/processconfig.go
+++ b/pkg/pipelineascode/processconfig.go
@@ -11,11 +11,13 @@ import (
 )
 
 type tektonYaml struct {
-	Tasks []string `yaml:"metadata,omitempty"`
+	Tasks     []string `yaml:"metadata,omitempty"`
+	Namespace string   `yaml:"namespace,omitempty"`
 }
 
 type TektonYamlConfig struct {
 	RemoteTasks string
+	Namespace   string
 }
 
 const (
@@ -34,6 +36,8 @@ func processTektonYaml(cs *cli.Clients, runinfo *webvcs.RunInfo, data string) (T
 		return tyConfig, err
 	}
 
+	tyConfig.Namespace = ty.Namespace
+
 	// parse tasks syntax:
 	// - http(s)?://foo -> url
 	// - foo/bar -> internal file inside repo
@@ -51,7 +55,7 @@ func processTektonYaml(cs *cli.Clients, runinfo *webvcs.RunInfo, data string) (T
 			defer res.Body.Close()
 			tyConfig.RemoteTasks += addTaskYamlDocuments(string(data))
 		case strings.Contains(task, "/"):
-			data, err := cs.GithubClient.GetFileInsideRepo(task, runinfo)
+			data, err := cs.GithubClient.GetFileInsideRepo(task, false, runinfo)
 			if err != nil {
 				return tyConfig, err
 			}
diff --git a/pkg/webvcs/github.go b/pkg/webvcs/github.go
index 8c8f1b9b..ce55186b 100644
--- a/pkg/webvcs/github.go
+++ b/pkg/webvcs/github.go
@@ -29,6 +29,11 @@ type RunInfo struct {
 	CheckRunID    *int64
 }
 
+// DeepCopyInto deep copy runinfo in another instance
+func (r *RunInfo) DeepCopyInto(out *RunInfo) {
+	*out = *r
+}
+
 func NewGithubVCS(token string) GithubVCS {
 	ctx := context.Background()
 	ts := oauth2.StaticTokenSource(
@@ -99,9 +104,16 @@ func (v GithubVCS) GetTektonDir(path string, runinfo *RunInfo) ([]*github.Reposi
 	return objects, nil
 }
 
-func (v GithubVCS) GetFileInsideRepo(path string, runinfo *RunInfo) (string, error) {
+// GetFileInsideRepo Get a file via Github API using the runinfo information, we
+// branch is true, the user the branch as ref isntead of the SHA
+func (v GithubVCS) GetFileInsideRepo(path string, branch bool, runinfo *RunInfo) (string, error) {
+	ref := runinfo.SHA
+	if branch {
+		ref = runinfo.Branch
+	}
+
 	fp, objects, resp, err := v.Client.Repositories.GetContents(v.Context, runinfo.Owner,
-		runinfo.Repository, path, &github.RepositoryContentGetOptions{Ref: runinfo.SHA})
+		runinfo.Repository, path, &github.RepositoryContentGetOptions{Ref: ref})
 
 	if err != nil {
 		return "", err
@@ -121,6 +133,20 @@ func (v GithubVCS) GetFileInsideRepo(path string, runinfo *RunInfo) (string, err
 	return string(getobj), nil
 }
 
+// GetFileFromDefaultBranch will get a file directly from the Default Branch as
+// configured in runinfo which is directly set in webhook by Github
+func (v GithubVCS) GetFileFromDefaultBranch(path string, runinfo *RunInfo) (string, error) {
+	var runInfoOnMain = &RunInfo{}
+	runinfo.DeepCopyInto(runInfoOnMain)
+	runInfoOnMain.Branch = runInfoOnMain.DefaultBranch
+
+	tektonyaml, err := v.GetFileInsideRepo(path, true, runInfoOnMain)
+	if err != nil {
+		return "", fmt.Errorf("Cannot find %s inside the \"%s\" branch: %s", path, runInfoOnMain.Branch, err)
+	}
+	return tektonyaml, err
+}
+
 func (v GithubVCS) GetTektonDirTemplate(objects []*github.RepositoryContent, runinfo *RunInfo) (string, error) {
 	var allTemplates string
 


Test patch:
diff --git a/pkg/pipelineascode/pipelineascode_test.go b/pkg/pipelineascode/pipelineascode_test.go
index aff917b1..64f20bbe 100644
--- a/pkg/pipelineascode/pipelineascode_test.go
+++ b/pkg/pipelineascode/pipelineascode_test.go
@@ -40,12 +40,20 @@ func (t *FakeTektonClient) PipelineRunDescribe(string, string) (string, error) {
 	return t.describeOutput, nil
 }
 
-func newRepo(name, url, branch, eventType, namespace string) *v1alpha1.Repository {
+func newRepo(name, url, branch, eventType, installNamespace, namespace string) *v1alpha1.Repository {
 	cw := clockwork.NewFakeClock()
 	return &v1alpha1.Repository{
-		TypeMeta:   metav1.TypeMeta{},
-		ObjectMeta: metav1.ObjectMeta{Name: name},
-		Spec:       v1alpha1.RepositorySpec{Namespace: namespace, URL: url, Branch: branch, EventType: eventType},
+		TypeMeta: metav1.TypeMeta{},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: installNamespace,
+		},
+		Spec: v1alpha1.RepositorySpec{
+			Namespace: namespace,
+			URL:       url,
+			EventType: eventType,
+			Branch:    branch,
+		},
 		Status: []v1alpha1.RepositoryRunStatus{
 			{
 				Status:          v1beta1.Status{},
@@ -87,54 +95,142 @@ func replyString(mux *http.ServeMux, url, body string) {
 	})
 }
 
-func TestFilterBy(t *testing.T) {
+func TestFilterByGood(t *testing.T) {
 	ctx, _ := rtesting.SetupFakeContext(t)
-	testParams := []struct {
-		name, namespace, url, branch, eventType string
-		nomatch                                 bool
-		repositories                            []*v1alpha1.Repository
-	}{
-		{
-			name:         "test-good",
-			repositories: []*v1alpha1.Repository{newRepo("test-good", "https://foo/bar", "lovedone", "pull_request", "namespace")},
-			url:          "https://foo/bar",
-			eventType:    "pull_request",
-			branch:       "lovedone",
-			namespace:    "namespace",
+
+	// Good and matching
+	eventType := "pull_request"
+	branch := "mainone"
+	targetNamespace := "namespace"
+	url := "https://psg.fr"
+	d := test.Data{
+		Repositories: []*v1alpha1.Repository{
+			newRepo("test-good", url, branch, eventType, targetNamespace, targetNamespace),
 		},
-		{
-			name:         "test-notmatch",
-			repositories: []*v1alpha1.Repository{newRepo("test-notmatch", "https://foo/bar", "lovedone", "pull_request", "namespace")},
-			url:          "https://xyz/vlad",
-			eventType:    "pull_request",
-			branch:       "lovedone",
-			namespace:    "namespace",
-			nomatch:      true,
+	}
+	cs, _ := test.SeedTestData(t, ctx, d)
+	client := &cli.Clients{PipelineAsCode: cs.PipelineAsCode}
+	repo, err := getRepoByCR(client, url, branch, eventType, "")
+	assert.NilError(t, err)
+	assert.Equal(t, repo.Spec.Namespace, targetNamespace)
+}
+
+func TestFilterByNotMatch(t *testing.T) {
+	ctx, _ := rtesting.SetupFakeContext(t)
+
+	// Not matching
+	eventType := "pull_request"
+	branch := "mainone"
+	targetNamespace := "namespace"
+	url := "https://psg.com"
+	otherurl := "https://marseille.com"
+	d := test.Data{
+		Repositories: []*v1alpha1.Repository{
+			newRepo("test-notmatch", url, branch, eventType, targetNamespace, targetNamespace),
+		},
+	}
+	cs, _ := test.SeedTestData(t, ctx, d)
+	client := &cli.Clients{PipelineAsCode: cs.PipelineAsCode}
+	repo, err := getRepoByCR(client, otherurl, branch, eventType, "")
+	assert.NilError(t, err)
+	assert.Equal(t, repo.Spec.Namespace, "")
+}
+
+func TestFilterByNotInItsNamespace(t *testing.T) {
+	// The CR should belong to the namespace it target
+	ctx, _ := rtesting.SetupFakeContext(t)
+	testname := "test-not-in-its-namespace"
+	eventType := "pull_request"
+	branch := "mainone"
+	targetNamespace := "namespace"
+	installNamespace := "olympiquelyon"
+	url := "https://psg.fr"
+	d := test.Data{
+		Repositories: []*v1alpha1.Repository{
+			newRepo(testname, url, branch, eventType, installNamespace, targetNamespace),
 		},
 	}
+	cs, _ := test.SeedTestData(t, ctx, d)
+	client := &cli.Clients{PipelineAsCode: cs.PipelineAsCode}
+	repo, err := getRepoByCR(client, url, branch, eventType, "")
+	assert.ErrorContains(t, err, fmt.Sprintf("Repo CR %s matches but belongs to", testname))
+	assert.Equal(t, repo.Spec.Namespace, "")
+}
 
-	for _, tp := range testParams {
-		t.Run(tp.name, func(t *testing.T) {
-			d := test.Data{
-				Repositories: tp.repositories,
-			}
-			cs, _ := test.SeedTestData(t, ctx, d)
-			repo, err := getRepoByCRD(&cli.Clients{
-				PipelineAsCode: cs.PipelineAsCode,
-			}, tp.url, tp.branch, tp.eventType)
-			if err != nil {
-				t.Fatal(err)
-			}
+func TestFilterForceNamespace(t *testing.T) {
+	// The CR should belong to the namespace it target
+	ctx, _ := rtesting.SetupFakeContext(t)
+	testname := "test-not-in-its-namespace"
+	eventType := "pull_request"
+	branch := "mainone"
+	targetNamespace := "namespace"
+	forcedNamespace := "asmonaco"
+
+	url := "https://psg.fr"
+	d := test.Data{
+		Repositories: []*v1alpha1.Repository{
+			newRepo(testname, url, branch, eventType, targetNamespace, targetNamespace),
+		},
+	}
+	cs, _ := test.SeedTestData(t, ctx, d)
+	client := &cli.Clients{PipelineAsCode: cs.PipelineAsCode}
+	_, err := getRepoByCR(client, url, branch, eventType, forcedNamespace)
+	assert.ErrorContains(t, err, "as configured from tekton.yaml on the main branch")
+}
 
-			if tp.nomatch {
-				assert.Equal(t, repo.Spec.Namespace, "")
+func TestRunDeniedFromForcedNamespace(t *testing.T) {
+	ctx, _ := rtesting.SetupFakeContext(t)
+	fakeclient, mux, _, teardown := testhelper.SetupGH()
+	defer teardown()
+	var defaultBranch = "default"
+	var forcedNamespace = "laotronamspace"
+	var installedNamespace = "nomespace"
+	var runinfo = &webvcs.RunInfo{
+		SHA:           "principale",
+		Owner:         "chmouel",
+		Repository:    "repo",
+		URL:           "https://service/documentation",
+		Branch:        "press",
+		DefaultBranch: defaultBranch,
+	}
+	replyString(mux,
+		fmt.Sprintf("/repos/%s/%s/check-runs", runinfo.Owner, runinfo.Repository),
+		`{"id": 26}`)
+
+	mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/contents/.tekton/tekton.yaml", runinfo.Owner, runinfo.Repository),
+		func(w http.ResponseWriter, r *http.Request) {
+			if r.URL.Query().Get("ref") != defaultBranch {
+				fmt.Fprint(w, `{}`)
 			} else {
-				assert.Equal(t, repo.Spec.Namespace, tp.namespace)
+				fmt.Fprint(w, `{"sha": "shaofmaintektonyaml"}`)
 			}
 		})
 
+	forcedNamespaceContent := base64.RawStdEncoding.EncodeToString([]byte("namespace: "+forcedNamespace+"\n")) + "="
+	mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/git/blobs/shaofmaintektonyaml", runinfo.Owner, runinfo.Repository),
+		func(w http.ResponseWriter, r *http.Request) {
+			fmt.Fprintf(w, `{"content": "%s"}`, forcedNamespaceContent)
+		})
+
+	gcvs := webvcs.GithubVCS{
+		Client:  fakeclient,
+		Context: ctx,
+	}
+	datas := test.Data{
+		Repositories: []*v1alpha1.Repository{
+			newRepo("repo", runinfo.URL, runinfo.Branch, "pull_request", installedNamespace, installedNamespace),
+		},
+	}
+	stdata, _ := test.SeedTestData(t, ctx, datas)
+	cs := &cli.Clients{
+		GithubClient:   gcvs,
+		PipelineAsCode: stdata.PipelineAsCode,
 	}
+	err := Run(cs, runinfo)
 
+	assert.Error(t, err,
+		fmt.Sprintf("Repo CR matches but should be installed in \"%s\" as configured from tekton.yaml on the main branch",
+			forcedNamespace))
 }
 
 func TestRun(t *testing.T) {
@@ -241,6 +337,7 @@ func TestRun(t *testing.T) {
 			runinfo.URL,
 			runinfo.Branch,
 			"pull_request",
+			"namespace",
 			"namespace"),
 		},
 	}
@@ -265,7 +362,8 @@ func TestRun(t *testing.T) {
 	err = Run(cs, runinfo)
 	assert.NilError(t, err)
 	assert.Assert(t, len(log.TakeAll()) > 0)
-	got, err := stdata.PipelineAsCode.PipelinesascodeV1alpha1().Repositories().Get(ctx, "test-run", metav1.GetOptions{})
+	got, err := stdata.PipelineAsCode.PipelinesascodeV1alpha1().Repositories("namespace").Get(
+		ctx, "test-run", metav1.GetOptions{})
 	assert.NilError(t, err)
 	// TODO: we could not fake creation, so it's empty
 	// but we can test that the last one doesn't match to what we had
diff --git a/pkg/pipelineascode/processconfig_test.go b/pkg/pipelineascode/processconfig_test.go
index 9f2cd939..c2f3ef8e 100644
--- a/pkg/pipelineascode/processconfig_test.go
+++ b/pkg/pipelineascode/processconfig_test.go
@@ -16,6 +16,15 @@ import (
 	"gotest.tools/assert"
 )
 
+func TestProcessTektonYamlNamespace(t *testing.T) {
+	namespace := "liguedestalents"
+	data := "namespace: " + namespace
+	cs := cli.Clients{}
+	ret, err := processTektonYaml(&cs, &webvcs.RunInfo{}, data)
+	assert.NilError(t, err)
+	assert.Equal(t, ret.Namespace, namespace)
+}
+
 func TestProcessTektonYamlRemoteTask(t *testing.T) {
 	data := `tasks:
 - https://foo.bar
diff --git a/pkg/test/helpers.go b/pkg/test/helpers.go
index 1536d3ca..ddeabf92 100644
--- a/pkg/test/helpers.go
+++ b/pkg/test/helpers.go
@@ -112,7 +112,7 @@ func SeedTestData(t *testing.T, ctx context.Context, d Data) (Clients, Informers
 		if err := i.Repository.Informer().GetIndexer().Add(repo); err != nil {
 			t.Fatal(err)
 		}
-		if _, err := c.PipelineAsCode.PipelinesascodeV1alpha1().Repositories().Create(ctx, repo, metav1.CreateOptions{}); err != nil {
+		if _, err := c.PipelineAsCode.PipelinesascodeV1alpha1().Repositories(repo.Namespace).Create(ctx, repo, metav1.CreateOptions{}); err != nil {
 			t.Fatal(err)
 		}
 
diff --git a/pkg/webvcs/github_test.go b/pkg/webvcs/github_test.go
index 27dea083..cf3a7a1c 100644
--- a/pkg/webvcs/github_test.go
+++ b/pkg/webvcs/github_test.go
@@ -206,7 +206,7 @@ func TestGetFileInsideRepo(t *testing.T) {
 	}
 	for _, tt := range testGetTektonDir {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := gcvs.GetFileInsideRepo(tt.args.path, tt.args.runinfo)
+			got, err := gcvs.GetFileInsideRepo(tt.args.path, false, tt.args.runinfo)
 			tt.args.assertion(t, got, err)
 		})
 	}