Repository: tektoncd/operator
Pull request #846 (2022-06-20 07:48:52)

Adds Tekton Hub ownerRef for Hub components
- Currently, there is no ownerRef added for secrets and configMap
of db, api and ui

- This patch adds an ownerRef of `TektonHub` when Hub instance is
created

 **Note** : If the secrets of db and api are created by user then those
one's are not deleted

Signed-off-by: Puneet Punamiya ppunamiy@redhat.com

# Changes

<!-- 🎉🎉🎉 Thank you for the PR!!! 🎉🎉🎉 -->

<!-- Describe your changes here- ideally you can get that description straight from
your descriptive commit message(s)! -->

# Submitter Checklist

These are the criteria that every PR should meet, please check them off as you
review them:

- [ ] Includes [tests](https://github.com/tektoncd/community/blob/master/standards.md#principles) (if functionality changed/added)
- [ ] Includes [docs](https://github.com/tektoncd/community/blob/master/standards.md#principles) (if user facing)
- [ ] Commit messages follow [commit message best practices](https://github.com/tektoncd/community/blob/master/standards.md#commit-messages)

_See [the contribution guide](https://github.com/tektoncd/operator/blob/master/CONTRIBUTING.md) for more details._

# Release Notes

<!--
Describe any user facing changes here, or delete this block.

Examples of user facing changes:
- API changes
- Bug fixes
- Any changes in behavior
- Changes requiring upgrade notices or deprecation warnings

For pull requests with a release note:

```release-note
Your release note here
```

For pull requests that require additional action from users switching to the new release, include the string "action required" (case insensitive) in the release note:

```release-note
action required: your release note here
```

For pull requests that don't need to be mentioned at release time, use the `/release-note-none` Prow command to add the `release-note-none` label to the PR. You can also write the string "NONE" as a release note in your PR description:

```release-note
NONE
```
-->


tekton-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **houshengbo**
You can assign the PR to them by writing `/assign @houshengbo` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=tektoncd%2Foperator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/tektoncd/operator/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["houshengbo"]} -->
tekton-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/tektoncd/operator/pull/846#issuecomment-1160205066" title="Approved">nikhil-thomas</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=tektoncd%2Foperator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/tektoncd/operator/blob/main/OWNERS)~~ [nikhil-thomas]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
nikhil-thomas: @PuneetPunamiya add a release not section in your pr description.
if it does not need one, the add the release note section. (as mentioned at the end of pr template).
tekton-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/tektoncd/operator/pull/846#issuecomment-1160205066" title="Approved">nikhil-thomas</a>*, *<a href="https://github.com/tektoncd/operator/pull/846#pullrequestreview-1018422937" title="LGTM">vdemeester</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=tektoncd%2Foperator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/tektoncd/operator/blob/main/OWNERS)~~ [nikhil-thomas,vdemeester]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
tekton-robot: @PuneetPunamiya: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

Patch:
diff --git a/cmd/openshift/operator/kodata/tekton-addon/pipelines-as-code-templates/rbac.yaml b/cmd/openshift/operator/kodata/tekton-addon/pipelines-as-code-templates/rbac.yaml
new file mode 100644
index 000000000..536208ce1
--- /dev/null
+++ b/cmd/openshift/operator/kodata/tekton-addon/pipelines-as-code-templates/rbac.yaml
@@ -0,0 +1,30 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: pipelines-as-code-templates
+  labels:
+    app.kubernetes.io/part-of: pipelines-as-code
+rules:
+  # All system:authenticated users needs to have access
+  # of the pipelines-as-code-templates ConfigMap even if they don't
+  # have access to the other resources present in the
+  # installed namespace.
+  - apiGroups: [""]
+    resources: ["configmaps"]
+    verbs: ["get", "list"]
+    resourceNames: ["pipelines-as-code-go-template", "pipelines-as-code-java-template", "pipelines-as-code-nodejs-template", "pipelines-as-code-python-template"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: pipelines-as-code-templates
+  labels:
+    app.kubernetes.io/part-of: pipelines-as-code
+subjects:
+  - kind: Group
+    name: system:authenticated
+    apiGroup: rbac.authorization.k8s.io
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: pipelines-as-code-templates
\ No newline at end of file
diff --git a/go.mod b/go.mod
index d10f64fe6..28049f459 100644
--- a/go.mod
+++ b/go.mod
@@ -34,6 +34,7 @@ require (
 	k8s.io/code-generator v0.23.5
 	knative.dev/pkg v0.0.0-20220329144915-0a1ec2e0d46c
 	sigs.k8s.io/controller-runtime v0.7.2
+	sigs.k8s.io/yaml v1.3.0
 )
 
 require (
@@ -256,7 +257,6 @@ require (
 	k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
 	sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
-	sigs.k8s.io/yaml v1.3.0 // indirect
 )
 
 go 1.17
diff --git a/hack/fetch-releases.sh b/hack/fetch-releases.sh
index 67c99ef61..65eaf1f16 100755
--- a/hack/fetch-releases.sh
+++ b/hack/fetch-releases.sh
@@ -122,6 +122,27 @@ release_yaml_pac() {
 
     echo "Info: Added $comp/$fileName:$version release yaml !!"
     echo ""
+
+    runtime=( go java nodejs python )
+    for run in "${runtime[@]}"
+    do
+      echo "fetching PipelineRun template for runtime: $run"
+
+      source="https://raw.githubusercontent.com/openshift-pipelines/pipelines-as-code/${version}/pkg/cmd/tknpac/generate/templates/${run}.yaml"
+      dest_dir="${ko_data}/tekton-addon/pipelines-as-code-templates"
+      mkdir -p ${dest_dir} || true
+      destination="${dest_dir}/${run}.yaml"
+
+      http_response=$(curl -s -o ${destination} -w "%{http_code}" ${source})
+      echo url: ${source}
+
+      if [[ $http_response != "200" ]]; then
+        echo "Error: failed to get pipelinerun template for $run, status code: $http_response"
+        exit 1
+      fi
+
+    done
+    echo ""
 }
 
 
diff --git a/operatorhub/openshift/config.yaml b/operatorhub/openshift/config.yaml
index 1482f3b8d..11dc145b0 100644
--- a/operatorhub/openshift/config.yaml
+++ b/operatorhub/openshift/config.yaml
@@ -6,6 +6,11 @@ image-substitutions:
     containerTargets:
     - deploymentName: openshift-pipelines-operator
       containerName: openshift-pipelines-operator-lifecycle
+- image: registry.redhat.io/openshift-pipelines/pipelines-rhel8-operator@
+  replaceLocations:
+    containerTargets:
+    - deploymentName: openshift-pipelines-operator
+      containerName: openshift-pipelines-operator-cluster-operations
 - image: registry.redhat.io/openshift-pipelines/pipelines-operator-proxy-rhel8@
   replaceLocations:
     envTargets:
diff --git a/pkg/reconciler/kubernetes/tektonchain/tektonchain.go b/pkg/reconciler/kubernetes/tektonchain/tektonchain.go
index d384b856d..0ab04d4f6 100644
--- a/pkg/reconciler/kubernetes/tektonchain/tektonchain.go
+++ b/pkg/reconciler/kubernetes/tektonchain/tektonchain.go
@@ -57,7 +57,10 @@ type Reconciler struct {
 var _ tektonchainreconciler.Interface = (*Reconciler)(nil)
 var _ tektonchainreconciler.Finalizer = (*Reconciler)(nil)
 
-const createdByValue = "TektonChain"
+const (
+	createdByValue          = "TektonChain"
+	secretChainInstallerset = "chain-secret"
+)
 
 var (
 	ls = metav1.LabelSelector{
@@ -66,6 +69,12 @@ var (
 			v1alpha1.InstallerSetType: v1alpha1.ChainResourceName,
 		},
 	}
+	secretLs = metav1.LabelSelector{
+		MatchLabels: map[string]string{
+			v1alpha1.CreatedByKey:     createdByValue,
+			v1alpha1.InstallerSetType: secretChainInstallerset,
+		},
+	}
 )
 
 // ReconcileKind compares the actual state with the desired, and attempts to
@@ -116,7 +125,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonChain
 	// Mark PreReconcile Complete
 	tc.Status.MarkPreReconcilerComplete()
 
-	// Check if a Tekton InstallerSet already exists, if not then create one
+	// Check if a Tekton Chain InstallerSet already exists, if not then create one
 	labelSelector, err := common.LabelSelector(ls)
 	if err != nil {
 		return err
@@ -136,7 +145,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonChain
 		return r.updateTektonChainStatus(tc, createdIs)
 	}
 
-	// If exists, then fetch the InstallerSet
+	// If exists, then fetch the Tekton Chain InstallerSet
 	installedTIS, err := r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
 		Get(ctx, existingInstallerSet, metav1.GetOptions{})
 	if err != nil {
@@ -154,13 +163,13 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonChain
 	installerSetTargetNamespace := installedTIS.Annotations[v1alpha1.TargetNamespaceKey]
 	installerSetReleaseVersion := installedTIS.Labels[v1alpha1.ReleaseVersionKey]
 
-	// Check if TargetNamespace of existing TektonInstallerSet is same as expected
-	// Check if Release Version in TektonInstallerSet is same as expected
-	// If any of the above things is not same then delete the existing TektonInstallerSet
+	// Check if TargetNamespace of existing Tekton Chain InstallerSet is same as expected
+	// Check if Release Version in Tekton Chain InstallerSet is same as expected
+	// If any of the above things is not same then delete the existing Tekton Chain InstallerSet
 	// and create a new with expected properties
 
 	if installerSetTargetNamespace != tc.Spec.TargetNamespace || installerSetReleaseVersion != r.operatorVersion {
-		// Delete the existing TektonInstallerSet
+		// Delete the existing Tekton Chain InstallerSet
 		err := r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
 			Delete(ctx, existingInstallerSet, metav1.DeleteOptions{})
 		if err != nil {
@@ -168,7 +177,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonChain
 			return err
 		}
 
-		// Make sure the TektonInstallerSet is deleted
+		// Make sure the Tekton Chain InstallerSet is deleted
 		_, err = r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
 			Get(ctx, existingInstallerSet, metav1.GetOptions{})
 		if err == nil {
@@ -184,7 +193,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonChain
 	} else {
 		// If target namespace and version are not changed then check if Chain
 		// spec is changed by checking hash stored as annotation on
-		// TektonInstallerSet with computing new hash of TektonChain Spec
+		// Tekton Chain InstallerSet with computing new hash of TektonChain Spec
 
 		// Hash of TektonChain Spec
 		expectedSpecHash, err := hash.Compute(tc.Spec)
@@ -209,6 +218,8 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonChain
 			if tc.Spec.GetTargetNamespace() == pipelineNamespace {
 				manifest = manifest.Filter(mf.Not(mf.ByKind("Namespace")))
 			}
+			// remove secret from this installerset as this installerset will be deleted on upgrade
+			manifest = manifest.Filter(mf.Not(mf.ByKind("Secret")))
 			if err := r.transform(ctx, &manifest, tc); err != nil {
 				logger.Error("manifest transformation failed:  ", err)
 				return err
@@ -233,6 +244,64 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonChain
 		}
 	}
 
+	// Check if a Tekton Chain Secret InstallerSet already exists, if not then create one
+	secretLabelSelector, err := common.LabelSelector(secretLs)
+	if err != nil {
+		return err
+	}
+	existingSecretInstallerSet, err := tektoninstallerset.CurrentInstallerSetName(ctx, r.operatorClientSet, secretLabelSelector)
+	if err != nil {
+		return err
+	}
+	if existingSecretInstallerSet == "" {
+		tc.Status.MarkInstallerSetNotAvailable("Chain Secret InstallerSet not available")
+		_, err := r.createSecretInstallerSet(ctx, tc)
+		if err != nil {
+			return err
+		}
+		return v1alpha1.RECONCILE_AGAIN_ERR
+	}
+
+	// If exists, then fetch the Tekton Chain Secret InstallerSet
+	installedSecretTIS, err := r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
+		Get(ctx, existingSecretInstallerSet, metav1.GetOptions{})
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			_, err := r.createSecretInstallerSet(ctx, tc)
+			if err != nil {
+				return err
+			}
+			return v1alpha1.RECONCILE_AGAIN_ERR
+		}
+		logger.Error("failed to get InstallerSet: %s", err)
+		return err
+	}
+
+	secretInstallerSetTargetNamespace := installedSecretTIS.Annotations[v1alpha1.TargetNamespaceKey]
+	// if the namespace has been changed for chainsCR, then deleted the Tekton Chain Secret Installerset
+	if secretInstallerSetTargetNamespace != tc.Spec.TargetNamespace {
+		// Delete the existing Tekton Chain Secret InstallerSet
+		err := r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
+			Delete(ctx, existingSecretInstallerSet, metav1.DeleteOptions{})
+		if err != nil {
+			logger.Error("failed to delete TektonChainSecret InstallerSet: %s", err)
+			return err
+		}
+
+		// Make sure the Tekton Chain Secret InstallerSet is deleted
+		_, err = r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
+			Get(ctx, existingSecretInstallerSet, metav1.GetOptions{})
+		if err == nil {
+			tc.Status.MarkNotReady("Waiting for previous installer set to get deleted")
+			return v1alpha1.REQUEUE_EVENT_AFTER
+		}
+		if !apierrors.IsNotFound(err) {
+			logger.Error("failed to get InstallerSet: %s", err)
+			return err
+		}
+		return nil
+	}
+
 	// Mark InstallerSetAvailable
 	tc.Status.MarkInstallerSetAvailable()
 
@@ -336,11 +405,17 @@ func (r *Reconciler) createInstallerSet(ctx context.Context, tc *v1alpha1.Tekton
 	if tc.Spec.GetTargetNamespace() == pipelineNamespace {
 		manifest = manifest.Filter(mf.Not(mf.ByKind("Namespace")))
 	}
+
+	// remove secret from this installerset as this installerset will be deleted on upgrade
+	manifest = manifest.Filter(mf.Not(mf.ByKind("Secret")))
 	if err := r.transform(ctx, &manifest, tc); err != nil {
 		tc.Status.MarkNotReady("transformation failed: " + err.Error())
 		return nil, err
 	}
 
+	// generate installer set
+	tis := makeInstallerSet(tc, manifest, v1alpha1.ChainResourceName, r.operatorVersion)
+
 	// compute the hash of tektonchain spec and store as an annotation
 	// in further reconciliation we compute hash of tc spec and check with
 	// annotation, if they are same then we skip updating the object
@@ -349,9 +424,32 @@ func (r *Reconciler) createInstallerSet(ctx context.Context, tc *v1alpha1.Tekton
 	if err != nil {
 		return nil, err
 	}
+	tis.Annotations[v1alpha1.LastAppliedHashKey] = specHash
+
+	// create installer set
+	createdIs, err := r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
+		Create(ctx, tis, metav1.CreateOptions{})
+	if err != nil {
+		return nil, err
+	}
+	return createdIs, nil
+}
+
+func (r *Reconciler) createSecretInstallerSet(ctx context.Context, tc *v1alpha1.TektonChain) (*v1alpha1.TektonInstallerSet, error) {
+
+	manifest := r.manifest
+	// filter only secret for this installerset as this needs
+	// to be restored over upgrade
+	manifest = manifest.Filter(mf.ByKind("Secret"))
+	if err := r.transform(ctx, &manifest, tc); err != nil {
+		tc.Status.MarkNotReady("transformation failed: " + err.Error())
+		return nil, err
+	}
+
+	// generate installer set
+	tis := makeInstallerSet(tc, manifest, secretChainInstallerset, r.operatorVersion)
 
 	// create installer set
-	tis := makeInstallerSet(tc, manifest, specHash, r.operatorVersion)
 	createdIs, err := r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets().
 		Create(ctx, tis, metav1.CreateOptions{})
 	if err != nil {
@@ -360,19 +458,18 @@ func (r *Reconciler) createInstallerSet(ctx context.Context, tc *v1alpha1.Tekton
 	return createdIs, nil
 }
 
-func makeInstallerSet(tc *v1alpha1.TektonChain, manifest mf.Manifest, tdSpecHash, releaseVersion string) *v1alpha1.TektonInstallerSet {
+func makeInstallerSet(tc *v1alpha1.TektonChain, manifest mf.Manifest, installerSetType, releaseVersion string) *v1alpha1.TektonInstallerSet {
 	ownerRef := *metav1.NewControllerRef(tc, tc.GetGroupVersionKind())
 	return &v1alpha1.TektonInstallerSet{
 		ObjectMeta: metav1.ObjectMeta{
-			GenerateName: fmt.Sprintf("%s-", v1alpha1.ChainResourceName),
+			GenerateName: fmt.Sprintf("%s-", installerSetType),
 			Labels: map[string]string{
 				v1alpha1.CreatedByKey:      createdByValue,
 				v1alpha1.ReleaseVersionKey: releaseVersion,
-				v1alpha1.InstallerSetType:  v1alpha1.ChainResourceName,
+				v1alpha1.InstallerSetType:  installerSetType,
 			},
 			Annotations: map[string]string{
 				v1alpha1.TargetNamespaceKey: tc.Spec.TargetNamespace,
-				v1alpha1.LastAppliedHashKey: tdSpecHash,
 			},
 			OwnerReferences: []metav1.OwnerReference{ownerRef},
 		},
diff --git a/pkg/reconciler/kubernetes/tektonhub/tektonhub.go b/pkg/reconciler/kubernetes/tektonhub/tektonhub.go
index ec9db24ac..3996bbf5b 100644
--- a/pkg/reconciler/kubernetes/tektonhub/tektonhub.go
+++ b/pkg/reconciler/kubernetes/tektonhub/tektonhub.go
@@ -386,7 +386,7 @@ func (r *Reconciler) validateOrCreateDBSecrets(ctx context.Context, th *v1alpha1
 
 	dbSecret, err := r.getSecret(ctx, dbSecretName, namespace, dbKeys)
 	if err != nil {
-		newDbSecret := createDbSecret(dbSecretName, namespace, dbSecret)
+		newDbSecret := createDbSecret(dbSecretName, namespace, dbSecret, th)
 		if apierrors.IsNotFound(err) {
 			_, err = r.kubeClientSet.CoreV1().Secrets(namespace).Create(ctx, newDbSecret, metav1.CreateOptions{})
 			if err != nil {
@@ -496,6 +496,7 @@ func createUiConfigMap(name, namespace string, th *v1alpha1.TektonHub) *corev1.C
 			Labels: map[string]string{
 				"ui": "tektonhub-ui",
 			},
+			OwnerReferences: []metav1.OwnerReference{getOwnerRef(th)},
 		},
 		Data: map[string]string{
 			"API_URL":       th.Status.ApiRouteUrl,
@@ -946,6 +947,7 @@ func createApiConfigMap(name, namespace string, th *v1alpha1.TektonHub) *corev1.
 			Labels: map[string]string{
 				"app": "api",
 			},
+			OwnerReferences: []metav1.OwnerReference{getOwnerRef(th)},
 		},
 		Data: map[string]string{
 			"CONFIG_FILE_URL":          th.Spec.Api.HubConfigUrl,
@@ -987,7 +989,7 @@ func (r *Reconciler) getHubManifest(ctx context.Context, th *v1alpha1.TektonHub,
 	return manifest, nil
 }
 
-func createDbSecret(name, namespace string, existingSecret *corev1.Secret) *corev1.Secret {
+func createDbSecret(name, namespace string, existingSecret *corev1.Secret, th *v1alpha1.TektonHub) *corev1.Secret {
 	s := &corev1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      name,
@@ -995,6 +997,7 @@ func createDbSecret(name, namespace string, existingSecret *corev1.Secret) *core
 			Labels: map[string]string{
 				"app": "db",
 			},
+			OwnerReferences: []metav1.OwnerReference{getOwnerRef(th)},
 		},
 		Type: corev1.SecretTypeOpaque,
 	}
@@ -1028,6 +1031,11 @@ func createDbSecret(name, namespace string, existingSecret *corev1.Secret) *core
 	return s
 }
 
+// Get an ownerRef of TektonHub
+func getOwnerRef(th *v1alpha1.TektonHub) metav1.OwnerReference {
+	return *metav1.NewControllerRef(th, th.GroupVersionKind())
+}
+
 func (r *Reconciler) targetNamespaceCheck(ctx context.Context, th *v1alpha1.TektonHub) error {
 	_, err := r.kubeClientSet.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
 	if err != nil {
diff --git a/pkg/reconciler/openshift/tektonaddon/pipelinesascode.go b/pkg/reconciler/openshift/tektonaddon/pipelinesascode.go
index f98b293f2..53bb506c1 100644
--- a/pkg/reconciler/openshift/tektonaddon/pipelinesascode.go
+++ b/pkg/reconciler/openshift/tektonaddon/pipelinesascode.go
@@ -20,8 +20,15 @@ import (
 	"context"
 	"os"
 	"path/filepath"
+	"strings"
 
+	routev1 "github.com/openshift/api/route/v1"
+	"github.com/openshift/client-go/route/clientset/versioned/scheme"
 	"github.com/tektoncd/operator/pkg/reconciler/openshift"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/yaml"
 
 	mf "github.com/manifestival/manifestival"
 	"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
@@ -29,6 +36,17 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+const pacRuntimeLabel = "pipelinesascode.openshift.io/runtime"
+
+var configmapTemplate = `apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: pipelines-as-code-template
+  labels:
+    app.kubernetes.io/part-of: pipelines-as-code
+data:
+  template: ""`
+
 var pacLS = metav1.LabelSelector{
 	MatchLabels: map[string]string{
 		v1alpha1.InstallerSetType: PACInstallerSet,
@@ -43,7 +61,6 @@ func (r *Reconciler) EnsurePipelinesAsCode(ctx context.Context, ta *v1alpha1.Tek
 	}
 
 	if *ta.Spec.EnablePAC {
-
 		exist, err := checkIfInstallerSetExist(ctx, r.operatorClientSet, r.operatorVersion, pacLabelSelector)
 		if err != nil {
 			return err
@@ -51,6 +68,7 @@ func (r *Reconciler) EnsurePipelinesAsCode(ctx context.Context, ta *v1alpha1.Tek
 		if !exist {
 			return r.ensurePAC(ctx, ta)
 		}
+		return r.updateControllerURL(ta)
 
 	} else {
 		// if disabled then delete the installer Set if exist
@@ -58,19 +76,53 @@ func (r *Reconciler) EnsurePipelinesAsCode(ctx context.Context, ta *v1alpha1.Tek
 			return err
 		}
 	}
+	return nil
+}
+
+func (r *Reconciler) updateControllerURL(ta *v1alpha1.TektonAddon) error {
+	var err error
+	pacManifest := mf.Manifest{
+		Client: r.manifest.Client,
+	}
+
+	koDataDir := os.Getenv(common.KoEnvKey)
+	pacLocation := filepath.Join(koDataDir, "tekton-addon", "pipelines-as-code")
+	if err := common.AppendManifest(&pacManifest, pacLocation); err != nil {
+		return err
+	}
+	pacManifest, err = pacManifest.Transform(mf.InjectNamespace(ta.Spec.TargetNamespace))
+	if err != nil {
+		return err
+	}
+
+	route, err := getControllerRouteHost(&pacManifest)
+	if err != nil {
+		return err
+	}
+	if route == "" {
+		return v1alpha1.RECONCILE_AGAIN_ERR
+	}
 
+	if err := updateInfoConfigMap(route, &pacManifest); err != nil {
+		return err
+	}
 	return nil
 }
 
 func (r *Reconciler) ensurePAC(ctx context.Context, ta *v1alpha1.TektonAddon) error {
 	pacManifest := mf.Manifest{}
 
+	// core manifest
 	koDataDir := os.Getenv(common.KoEnvKey)
 	pacLocation := filepath.Join(koDataDir, "tekton-addon", "pipelines-as-code")
 	if err := common.AppendManifest(&pacManifest, pacLocation); err != nil {
 		return err
 	}
 
+	if err := fetchPRTemplates(&pacManifest); err != nil {
+		return err
+	}
+
 	// installerSet adds it's owner as namespace's owner
 	// so deleting tekton addon deletes target namespace too
 	// to skip it we filter out namespace
@@ -95,3 +147,111 @@ func (r *Reconciler) ensurePAC(ctx context.Context, ta *v1alpha1.TektonAddon) er
 
 	return nil
 }
+
+func fetchPRTemplates(manifest *mf.Manifest) error {
+	prManifests := mf.Manifest{}
+	koDataDir := os.Getenv(common.KoEnvKey)
+	templateLocation := filepath.Join(koDataDir, "tekton-addon", "pipelines-as-code-templates")
+	if err := common.AppendManifest(&prManifests, templateLocation); err != nil {
+		return err
+	}
+
+	cmManifest, err := pipelineRunToConfigMapConverter(&prManifests)
+	if err != nil {
+		return err
+	}
+	*manifest = manifest.Append(*cmManifest)
+	return nil
+}
+
+func pipelineRunToConfigMapConverter(prManifests *mf.Manifest) (*mf.Manifest, error) {
+	cm := &v1.ConfigMap{}
+	err := yaml.Unmarshal([]byte(configmapTemplate), cm)
+	if err != nil {
+		return nil, err
+	}
+
+	var temp []unstructured.Unstructured
+	for _, res := range prManifests.Resources() {
+		if res.GetKind() != "PipelineRun" {
+			temp = append(temp, res)
+			continue
+		}
+
+		data, err := yaml.Marshal(res.Object)
+		if err != nil {
+			return nil, err
+		}
+
+		// set pipelineRun
+		cm.Data["template"] = string(data)
+
+		// set metadata
+		prname := res.GetName()
+		cm.SetName("pipelines-as-code-" + prname)
+		cm.Labels[pacRuntimeLabel] = strings.TrimPrefix(prname, "pipelinerun-")
+
+		unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm)
+		if err != nil {
+			return nil, err
+		}
+
+		temp = append(temp, unstructured.Unstructured{Object: unstrObj})
+	}
+	manifest, _ := mf.ManifestFrom(mf.Slice(temp))
+	return &manifest, nil
+}
+
+func getControllerRouteHost(manifest *mf.Manifest) (string, error) {
+	var hostUrl string
+	for _, r := range manifest.Filter(mf.ByKind("Route")).Resources() {
+		u, err := manifest.Client.Get(&r)
+		if err != nil {
+			return "", err
+		}
+		if u.GetName() == "pipelines-as-code-controller" {
+			route := &routev1.Route{}
+			if err := scheme.Scheme.Convert(u, route, nil); err != nil {
+				return "", err
+			}
+			hostUrl = route.Spec.Host
+		}
+	}
+	return hostUrl, nil
+}
+
+func updateInfoConfigMap(route string, pacManifest *mf.Manifest) error {
+	for _, r := range pacManifest.Filter(mf.ByKind("ConfigMap")).Resources() {
+		if r.GetName() != "pipelines-as-code-info" {
+			continue
+		}
+		u, err := pacManifest.Client.Get(&r)
+		if err != nil {
+			return err
+		}
+		cm := &v1.ConfigMap{}
+		err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, cm)
+		if err != nil {
+			return err
+		}
+
+		// set controller url
+		if cm.Data["controller-url"] != "" {
+			return nil
+		}
+
+		cm.Data["controller-url"] = "https://" + route
+
+		unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm)
+		if err != nil {
+			return err
+		}
+		u.SetUnstructuredContent(unstrObj)
+
+		err = pacManifest.Client.Update(u)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}


Test patch:
diff --git a/pkg/reconciler/openshift/tektonaddon/pipelinesascode_test.go b/pkg/reconciler/openshift/tektonaddon/pipelinesascode_test.go
new file mode 100644
index 000000000..119d68477
--- /dev/null
+++ b/pkg/reconciler/openshift/tektonaddon/pipelinesascode_test.go
@@ -0,0 +1,39 @@
+package tektonaddon
+
+import (
+	"path"
+	"testing"
+
+	mf "github.com/manifestival/manifestival"
+	"gotest.tools/v3/assert"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestPipelineRunToConfigMapConverter(t *testing.T) {
+	testData := path.Join("testdata", "test-pac-pr-template.yaml")
+	manifest, err := mf.ManifestFrom(mf.Recursive(testData))
+	assert.NilError(t, err)
+
+	cmManifest, err := pipelineRunToConfigMapConverter(&manifest)
+	assert.NilError(t, err)
+
+	got := &v1.ConfigMap{}
+	err = runtime.DefaultUnstructuredConverter.FromUnstructured(cmManifest.Resources()[0].Object, got)
+	if err != nil {
+		assert.NilError(t, err)
+	}
+
+	testData = path.Join("testdata", "test-expected-pac-pr-template.yaml")
+	expectedManifest, err := mf.ManifestFrom(mf.Recursive(testData))
+	assert.NilError(t, err)
+
+	expected := &v1.ConfigMap{}
+	err = runtime.DefaultUnstructuredConverter.FromUnstructured(expectedManifest.Resources()[0].Object, expected)
+	if err != nil {
+		assert.NilError(t, err)
+	}
+
+	assert.DeepEqual(t, expected.GetName(), got.GetName())
+	assert.DeepEqual(t, expected.GetLabels(), got.GetLabels())
+}
diff --git a/pkg/reconciler/openshift/tektonaddon/testdata/test-expected-pac-pr-template.yaml b/pkg/reconciler/openshift/tektonaddon/testdata/test-expected-pac-pr-template.yaml
new file mode 100644
index 000000000..d8794a399
--- /dev/null
+++ b/pkg/reconciler/openshift/tektonaddon/testdata/test-expected-pac-pr-template.yaml
@@ -0,0 +1,69 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: pipelines-as-code-pipelinerun-go
+  labels:
+    pipelinesascode.openshift.io/runtime: "go"
+    app.kubernetes.io/part-of: pipelines-as-code
+data:
+  template: |
+    apiVersion: tekton.dev/v1beta1
+    kind: PipelineRun
+    metadata:
+      annotations:
+        pipelinesascode.tekton.dev/max-keep-runs: "5"
+        pipelinesascode.tekton.dev/on-event: pull_request
+        pipelinesascode.tekton.dev/on-target-branch: main
+        pipelinesascode.tekton.dev/task: git-clone
+        pipelinesascode.tekton.dev/task-1: '[golangci-lint]'
+      name: go-template
+    spec:
+      params:
+        - name: repo_url
+          value: "{{ repo_url }}"
+        - name: revision
+          value: "{{ revision }}"
+      pipelineSpec:
+        params:
+          - name: repo_url
+          - name: revision
+        workspaces:
+          - name: source
+          - name: basic-auth
+        tasks:
+          - name: fetch-repository
+            taskRef:
+              name: git-clone
+            workspaces:
+              - name: output
+                workspace: source
+              - name: basic-auth
+                workspace: basic-auth
+            params:
+              - name: url
+                value: $(params.repo_url)
+              - name: revision
+                value: $(params.revision)
+          - name: golangci-lint
+            taskRef:
+              name: golangci-lint
+            runAfter:
+              - fetch-repository
+            params:
+              - name: package
+                value: .
+            workspaces:
+              - name: source
+                workspace: source
+      workspaces:
+        - name: source
+          volumeClaimTemplate:
+            spec:
+              accessModes:
+                - ReadWriteOnce
+              resources:
+                requests:
+                  storage: 1Gi
+        - name: basic-auth
+          secret:
+            secretName: "{{ git_auth_secret }}"
diff --git a/pkg/reconciler/openshift/tektonaddon/testdata/test-pac-pr-template.yaml b/pkg/reconciler/openshift/tektonaddon/testdata/test-pac-pr-template.yaml
new file mode 100644
index 000000000..1c8da24fb
--- /dev/null
+++ b/pkg/reconciler/openshift/tektonaddon/testdata/test-pac-pr-template.yaml
@@ -0,0 +1,83 @@
+---
+apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+  name: pipelinerun-go
+  annotations:
+    # The event we are targeting as seen from the webhook payload
+    # this can be an array too, i.e: [pull_request, push]
+    pipelinesascode.tekton.dev/on-event: "pull_request"
+
+    # The branch or tag we are targeting (ie: main, refs/tags/*)
+    pipelinesascode.tekton.dev/on-target-branch: "main"
+
+    # Fetch the git-clone task from hub, we are able to reference later on it
+    # with taskRef and it will automatically be embedded into our pipeline.
+    pipelinesascode.tekton.dev/task: "git-clone"
+
+
+    # Task for Golang
+    pipelinesascode.tekton.dev/task-1: "[golangci-lint]"
+
+    # You can add more tasks in here to reuse, browse the one you like from here
+    # https://hub.tekton.dev/
+    # example:
+    # pipelinesascode.tekton.dev/task-2: "[maven, buildah]"
+
+    # How many runs we want to keep attached to this event
+    pipelinesascode.tekton.dev/max-keep-runs: "5"
+spec:
+  params:
+    # The variable with brackets are special to Pipelines as Code
+    # They will automatically be expanded with the events from Github.
+    - name: repo_url
+      value: "{{ repo_url }}"
+    - name: revision
+      value: "{{ revision }}"
+  pipelineSpec:
+    params:
+      - name: repo_url
+      - name: revision
+    workspaces:
+      - name: source
+      - name: basic-auth
+    tasks:
+      - name: fetch-repository
+        taskRef:
+          name: git-clone
+        workspaces:
+          - name: output
+            workspace: source
+          - name: basic-auth
+            workspace: basic-auth
+        params:
+          - name: url
+            value: $(params.repo_url)
+          - name: revision
+            value: $(params.revision)
+      - name: golangci-lint
+        taskRef:
+          name: golangci-lint
+        runAfter:
+          - fetch-repository
+        params:
+          - name: package
+            value: .
+        workspaces:
+          - name: source
+            workspace: source
+
+  workspaces:
+    - name: source
+      volumeClaimTemplate:
+        spec:
+          accessModes:
+            - ReadWriteOnce
+          resources:
+            requests:
+              storage: 1Gi
+    # This workspace will inject secret to help the git-clone task to be able to
+    # checkout the private repositories
+    - name: basic-auth
+      secret:
+        secretName: "{{ git_auth_secret }}"