Repository: tektoncd/operator (go)

Issue:
Tekton operator | Custom namespace deployment issue
# Expected Behavior

Successful deployment of all resources with everything working out of the box.

![image](https://github.com/user-attachments/assets/a6f09452-35f4-45f9-9820-46b20ff5d77b)

# Actual Behavior

Tekton dashboard cannot fetch the configmaps that contain the versions of the tekton components. Most likely due to a role or role binding issue that happens when you customize the `defaultTargetNamespace` value in the helm chart.

If you don't specify a custom namespace and let the operator deploy everything in the default `tekton-pipelines` namespace the issue is not present!

# Steps to Reproduce the Problem

1. Deploy Tekton Operator (tekton-operator-0.74.0) via HELM using the values.yaml [values.yaml.txt](https://github.com/user-attachments/files/18005694/values.yaml.txt)
2. Wait for the operator to finish with deployment
3. Create an ingress for the Tekton Dashboard and notice the errors

![image](https://github.com/user-attachments/assets/9cb1a960-9da9-4575-a09a-cec605f0a065)
![image](https://github.com/user-attachments/assets/6ede79c1-bdf7-4678-a024-26dad581d8ee)

Raw Error:

```
{"level":"error","ts":1733306990.525748,"caller":"endpoints/dashboard.go:27","msg":"Error getting the Tekton triggers info ConfigMap: configmaps \"triggers-info\" is forbidden: User \"system:serviceaccount:mmp-pipelines:tekton-dashboard\" cannot get resource \"configmaps\" in API group \"\" in the namespace \"tekton-pipelines\"","stacktrace":"github.com/tektoncd/dashboard/pkg/endpoints.getVersion\n\tgithub.com/tektoncd/dashboard/pkg/endpoints/dashboard.go:27\ngithub.com/tektoncd/dashboard/pkg/endpoints.getTriggersVersion\n\tgithub.com/tektoncd/dashboard/pkg/endpoints/dashboard.go:56\ngithub.com/tektoncd/dashboard/pkg/endpoints.IsTriggersInstalled\n\tgithub.com/tektoncd/dashboard/pkg/endpoints/dashboard.go:67\ngithub.com/tektoncd/dashboard/pkg/endpoints.Resource.GetProperties\n\tgithub.com/tektoncd/dashboard/pkg/endpoints/cluster.go:64\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2166\nnet/http.(*ServeMux).ServeHTTP\n\tnet/http/server.go:2683\ngithub.com/tektoncd/dashboard/pkg/csrf.(*csrf).ServeHTTP\n\tgithub.com/tektoncd/dashboard/pkg/csrf/csrf.go:74\nnet/http.serverHandler.ServeHTTP\n\tnet/http/server.go:3137\nnet/http.(*conn).serve\n\tnet/http/server.go:2039"}

{"level":"error","ts":1733306923.1582959,"caller":"endpoints/dashboard.go:27","msg":"Error getting the Tekton pipelines info ConfigMap: configmaps \"pipelines-info\" is forbidden: User \"system:serviceaccount:mmp-pipelines:tekton-dashboard\" cannot get resource \"configmaps\" in API group \"\" in the namespace \"tekton-pipelines\"","stacktrace":"github.com/tektoncd/dashboard/pkg/endpoints.getVersion\n\tgithub.com/tektoncd/dashboard/pkg/endpoints/dashboard.go:27\ngithub.com/tektoncd/dashboard/pkg/endpoints.getPipelineVersion\n\tgithub.com/tektoncd/dashboard/pkg/endpoints/dashboard.go:46\ngithub.com/tektoncd/dashboard/pkg/endpoints.Resource.GetProperties\n\tgithub.com/tektoncd/dashboard/pkg/endpoints/cluster.go:46\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2166\nnet/http.(*ServeMux).ServeHTTP\n\tnet/http/server.go:2683\ngithub.com/tektoncd/dashboard/pkg/csrf.(*csrf).ServeHTTP\n\tgithub.com/tektoncd/dashboard/pkg/csrf/csrf.go:74\nnet/http.serverHandler.ServeHTTP\n\tnet/http/server.go:3137\nnet/http.(*conn).serve\n\tnet/http/server.go:2039"}
```

# Additional Info

- Kubernetes version:

  **Output of `kubectl version`:**
  ```
  Client Version: v1.30.6
  Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
  Server Version: v1.30.6
  ```

- Tekton Pipeline version:

  **Output of `tkn version` or `kubectl get pods -n tekton-pipelines -l app=tekton-pipelines-controller -o=jsonpath='{.items[0].metadata.labels.version}'`**

  ```v0.65.2```
<!-- Any other additional information -->

Plain vanilla k8s, deployed via Kubespray on Ubuntu Server 24.04.1 LTS

Pull request description:
Ensure the configured `TargetNamespace` overrides the default namespace in the Dashboard's container args
# Changes

Fixes #2438 

<!-- 🎉🎉🎉 Thank you for the PR!!! 🎉🎉🎉 -->
- Remove `results` specific details from the common transformer code
- Ensure the configured `TargetNamespace`  overrides the default namespace in the Dashboard's container args

I've tested this locally and confirmed that the Operator created a deployment for the Dashboard with the correct namespace override per the workflow described in #2438 , however it would be good to have someone test and verify the effect on their end as well


# Submitter Checklist

These are the criteria that every PR should meet, please check them off as you
review them:

- [x] Run `make test lint` before submitting a PR
- [x] Includes [tests](https://github.com/tektoncd/community/blob/master/standards.md#principles) (if functionality changed/added)
- [x] Includes [docs](https://github.com/tektoncd/community/blob/master/standards.md#principles) (if user facing)
- [x] 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

```release-note
When `defaultTargetNamespace` is specified in the Tekton Config, the value will now override the Dashboard's container args `--pipelines-namespace` and `--triggers-namespace`.
```


Patch:
diff --git a/charts/tekton-operator/values.yaml b/charts/tekton-operator/values.yaml
index b791507c5..4264f70fd 100644
--- a/charts/tekton-operator/values.yaml
+++ b/charts/tekton-operator/values.yaml
@@ -56,7 +56,7 @@ pruner:
   image:
     # Container image for Tekton pruner. Defaults to gcr.
     repository: "ghcr.io/tektoncd/plumbing/tkn"
-    tag: "d1da68e766393c4b4eb162128f2c5fd2cee270828811a113fcda1e8a586e7471"
+    tag: "233de6c8b8583a34c2379fa98d42dba739146c9336e8d41b66030484357481ed"
 
 ## Configuration for the tekton-operator-webhook pod
 webhook:
diff --git a/components.yaml b/components.yaml
index 5cd1704f8..a0257d87b 100644
--- a/components.yaml
+++ b/components.yaml
@@ -12,7 +12,7 @@ manual-approval-gate:
   version: v0.4.1
 pipeline:
   github: tektoncd/pipeline
-  version: v0.66.0
+  version: v0.68.0
 pipelines-as-code:
   github: openshift-pipelines/pipelines-as-code
   version: v0.32.0
diff --git a/config/kubernetes/base/operator.yaml b/config/kubernetes/base/operator.yaml
index a876f2a75..bf12c9631 100644
--- a/config/kubernetes/base/operator.yaml
+++ b/config/kubernetes/base/operator.yaml
@@ -51,7 +51,7 @@ spec:
         - name: IMAGE_PIPELINES_PROXY
           value: ko://github.com/tektoncd/operator/cmd/kubernetes/proxy-webhook
         - name: IMAGE_JOB_PRUNER_TKN
-          value: ghcr.io/tektoncd/plumbing/tkn@sha256:d1da68e766393c4b4eb162128f2c5fd2cee270828811a113fcda1e8a586e7471
+          value: ghcr.io/tektoncd/plumbing/tkn@sha256:233de6c8b8583a34c2379fa98d42dba739146c9336e8d41b66030484357481ed
         - name: METRICS_DOMAIN
           value: tekton.dev/operator
         - name: VERSION
diff --git a/config/openshift/base/operator.yaml b/config/openshift/base/operator.yaml
index 31e9c9d4b..8a5818f8d 100644
--- a/config/openshift/base/operator.yaml
+++ b/config/openshift/base/operator.yaml
@@ -63,7 +63,7 @@ spec:
         - name: IMAGE_PIPELINES_PROXY
           value: ko://github.com/tektoncd/operator/cmd/openshift/proxy-webhook
         - name: IMAGE_JOB_PRUNER_TKN
-          value: ghcr.io/tektoncd/plumbing/tkn@sha256:d1da68e766393c4b4eb162128f2c5fd2cee270828811a113fcda1e8a586e7471
+          value: ghcr.io/tektoncd/plumbing/tkn@sha256:233de6c8b8583a34c2379fa98d42dba739146c9336e8d41b66030484357481ed
         - name: METRICS_DOMAIN
           value: tekton.dev/operator
         - name: VERSION
diff --git a/operatorhub/kubernetes/release-artifacts/bundle/manifests/tektoncd-operator.clusterserviceversion.yaml b/operatorhub/kubernetes/release-artifacts/bundle/manifests/tektoncd-operator.clusterserviceversion.yaml
index bf8521e66..932cdb994 100644
--- a/operatorhub/kubernetes/release-artifacts/bundle/manifests/tektoncd-operator.clusterserviceversion.yaml
+++ b/operatorhub/kubernetes/release-artifacts/bundle/manifests/tektoncd-operator.clusterserviceversion.yaml
@@ -815,7 +815,7 @@ spec:
                 - name: IMAGE_PIPELINES_PROXY
                   value: gcr.io/tekton-releases/github.com/tektoncd/operator/cmd/kubernetes/proxy-webhook@sha256:c2c3a38c2d26fc05d336e0e47c24a488a26cc6df8d72cd55e00ac05668d090d4
                 - name: IMAGE_JOB_PRUNER_TKN
-                  value: ghcr.io/tektoncd/plumbing/tkn@sha256:d1da68e766393c4b4eb162128f2c5fd2cee270828811a113fcda1e8a586e7471
+                  value: ghcr.io/tektoncd/plumbing/tkn@sha256:233de6c8b8583a34c2379fa98d42dba739146c9336e8d41b66030484357481ed
                 - name: METRICS_DOMAIN
                   value: tekton.dev/operator
                 - name: VERSION
diff --git a/operatorhub/openshift/manifests/bases/openshift-pipelines-operator-rh.clusterserviceversion.template.yaml b/operatorhub/openshift/manifests/bases/openshift-pipelines-operator-rh.clusterserviceversion.template.yaml
index 806acb2f6..0060a549b 100644
--- a/operatorhub/openshift/manifests/bases/openshift-pipelines-operator-rh.clusterserviceversion.template.yaml
+++ b/operatorhub/openshift/manifests/bases/openshift-pipelines-operator-rh.clusterserviceversion.template.yaml
@@ -309,7 +309,7 @@ spec:
 
     ### Components
 
-    - Tekton Pipelines: v0.66.0
+    - Tekton Pipelines: v0.68.0
     - Tekton Triggers: v0.30.1
     - Pipelines as Code: v0.32.0
     - Tekton Chains: v0.23.1
diff --git a/pkg/reconciler/common/transformers.go b/pkg/reconciler/common/transformers.go
index c2bd37e3f..0e7c72f76 100644
--- a/pkg/reconciler/common/transformers.go
+++ b/pkg/reconciler/common/transformers.go
@@ -51,12 +51,11 @@ const (
 	HubImagePrefix                = "IMAGE_HUB_"
 	DashboardImagePrefix          = "IMAGE_DASHBOARD_"
 
+	DefaultTargetNamespace = "tekton-pipelines"
+
 	ArgPrefix   = "arg_"
 	ParamPrefix = "param_"
 
-	resultAPIDeployment     = "tekton-results-api"
-	resultWatcherDeployment = "tekton-results-watcher"
-
 	runAsNonRootValue              = true
 	allowPrivilegedEscalationValue = false
 )
@@ -472,7 +471,7 @@ func injectNamespaceClusterRole(targetNamespace string) mf.Transformer {
 			if containsNamespaceResource && ok {
 				nm := []interface{}{}
 				for _, rn := range resourceNames.([]interface{}) {
-					if rn.(string) == "tekton-pipelines" {
+					if rn.(string) == DefaultTargetNamespace {
 						nm = append(nm, targetNamespace)
 					} else {
 						nm = append(nm, rn)
@@ -485,10 +484,10 @@ func injectNamespaceClusterRole(targetNamespace string) mf.Transformer {
 	}
 }
 
-// ReplaceNamespaceInDeploymentEnv replaces namespace in deployment's env var
-func ReplaceNamespaceInDeploymentEnv(targetNamespace string) mf.Transformer {
+// ReplaceNamespaceInDeploymentEnv replaces any instance of the default namespace string in the given deployments' env var
+func ReplaceNamespaceInDeploymentEnv(deploymentNames []string, targetNamespace string) mf.Transformer {
 	return func(u *unstructured.Unstructured) error {
-		if u.GetKind() != "Deployment" || !(u.GetName() == resultAPIDeployment || u.GetName() == resultWatcherDeployment) {
+		if u.GetKind() != "Deployment" || !slices.Contains(deploymentNames, u.GetName()) {
 			return nil
 		}
 
@@ -516,16 +515,16 @@ func replaceNamespaceInDBAddress(envs []corev1.EnvVar, targetNamespace string) [
 
 	for i, e := range envs {
 		if slices.Contains(req, e.Name) {
-			envs[i].Value = strings.ReplaceAll(e.Value, "tekton-pipelines", targetNamespace)
+			envs[i].Value = strings.ReplaceAll(e.Value, DefaultTargetNamespace, targetNamespace)
 		}
 	}
 	return envs
 }
 
-// ReplaceNamespaceInDeploymentArgs replaces namespace in deployment's args
-func ReplaceNamespaceInDeploymentArgs(targetNamespace string) mf.Transformer {
+// ReplaceNamespaceInDeploymentArgs replaces any instance of the default namespace in the given deployments' args
+func ReplaceNamespaceInDeploymentArgs(deploymentNames []string, targetNamespace string) mf.Transformer {
 	return func(u *unstructured.Unstructured) error {
-		if u.GetKind() != "Deployment" || u.GetName() != resultWatcherDeployment {
+		if u.GetKind() != "Deployment" || !slices.Contains(deploymentNames, u.GetName()) {
 			return nil
 		}
 
@@ -550,8 +549,8 @@ func ReplaceNamespaceInDeploymentArgs(targetNamespace string) mf.Transformer {
 
 func replaceNamespaceInContainerArg(container *corev1.Container, targetNamespace string) {
 	for i, a := range container.Args {
-		if strings.Contains(a, "tekton-pipelines") {
-			container.Args[i] = strings.ReplaceAll(a, "tekton-pipelines", targetNamespace)
+		if strings.Contains(a, DefaultTargetNamespace) {
+			container.Args[i] = strings.ReplaceAll(a, DefaultTargetNamespace, targetNamespace)
 		}
 	}
 }
diff --git a/pkg/reconciler/kubernetes/tektondashboard/transform.go b/pkg/reconciler/kubernetes/tektondashboard/transform.go
index fbdecc098..cf96e6379 100644
--- a/pkg/reconciler/kubernetes/tektondashboard/transform.go
+++ b/pkg/reconciler/kubernetes/tektondashboard/transform.go
@@ -33,6 +33,7 @@ const (
 func filterAndTransform(extension common.Extension) client.FilterAndTransform {
 	return func(ctx context.Context, manifest *mf.Manifest, comp v1alpha1.TektonComponent) (*mf.Manifest, error) {
 		dashboard := comp.(*v1alpha1.TektonDashboard)
+		targetNamespace := dashboard.Spec.GetTargetNamespace()
 
 		images := common.ToLowerCaseKeys(common.ImagesFromEnv(common.DashboardImagePrefix))
 
@@ -42,6 +43,7 @@ func filterAndTransform(extension common.Extension) client.FilterAndTransform {
 			common.AddConfiguration(dashboard.Spec.Config),
 			common.AddDeploymentRestrictedPSA(),
 			common.DeploymentImages(images),
+			common.ReplaceNamespaceInDeploymentArgs([]string{dashboardDeploymentName}, targetNamespace),
 		}
 		trns = append(trns, extra...)
 		if dashboard.Spec.ExternalLogs != "" {
@@ -54,7 +56,7 @@ func filterAndTransform(extension common.Extension) client.FilterAndTransform {
 
 		// additional options transformer
 		// always execute as last transformer, so that the values in options will be final update values on the manifests
-		if err := common.ExecuteAdditionalOptionsTransformer(ctx, manifest, dashboard.Spec.GetTargetNamespace(), dashboard.Spec.Dashboard.Options); err != nil {
+		if err := common.ExecuteAdditionalOptionsTransformer(ctx, manifest, targetNamespace, dashboard.Spec.Dashboard.Options); err != nil {
 			return &mf.Manifest{}, err
 		}
 
diff --git a/pkg/reconciler/kubernetes/tektonresult/transform.go b/pkg/reconciler/kubernetes/tektonresult/transform.go
index 12a6199b2..175a6b0e6 100644
--- a/pkg/reconciler/kubernetes/tektonresult/transform.go
+++ b/pkg/reconciler/kubernetes/tektonresult/transform.go
@@ -55,9 +55,13 @@ const (
 	loggingForwarderDelayDuration = "LOGGING_PLUGIN_FORWARDER_DELAY_DURATION"
 	logsAPIKey                    = "LOGS_API"
 	logsTypeKey                   = "LOGS_TYPE"
+
+	resultAPIDeployment     = "tekton-results-api"
+	resultWatcherDeployment = "tekton-results-watcher"
 )
 
 var (
+	resultDeployementNames = []string{resultAPIDeployment, resultWatcherDeployment}
 	// allowed property secret keys
 	allowedPropertySecretKeys = []string{
 		"S3_BUCKET_NAME",
@@ -81,8 +85,8 @@ func (r *Reconciler) transform(ctx context.Context, manifest *mf.Manifest, comp
 	extra := []mf.Transformer{
 		common.InjectOperandNameLabelOverwriteExisting(v1alpha1.OperandTektoncdResults),
 		common.ApplyProxySettings,
-		common.ReplaceNamespaceInDeploymentArgs(targetNs),
-		common.ReplaceNamespaceInDeploymentEnv(targetNs),
+		common.ReplaceNamespaceInDeploymentArgs([]string{resultWatcherDeployment}, targetNs),
+		common.ReplaceNamespaceInDeploymentEnv(resultDeployementNames, targetNs),
 		updateApiConfig(instance.Spec),
 		enablePVCLogging(instance.Spec.ResultsAPIProperties),
 		updateEnvWithSecretName(instance.Spec.ResultsAPIProperties),


Test patch:
diff --git a/pkg/reconciler/common/transformers_test.go b/pkg/reconciler/common/transformers_test.go
index 9f407a50a..fcd39363e 100644
--- a/pkg/reconciler/common/transformers_test.go
+++ b/pkg/reconciler/common/transformers_test.go
@@ -432,7 +432,7 @@ func TestReplaceNamespaceInDeploymentEnv(t *testing.T) {
 	manifest, err := mf.ManifestFrom(mf.Recursive(testData))
 	assertNoEror(t, err)
 
-	manifest, err = manifest.Transform(ReplaceNamespaceInDeploymentEnv("openshift-pipelines"))
+	manifest, err = manifest.Transform(ReplaceNamespaceInDeploymentEnv([]string{"tekton-results-watcher", "tekton-results-api"}, "openshift-pipelines"))
 	assertNoEror(t, err)
 
 	d := &appsv1.Deployment{}
@@ -456,7 +456,7 @@ func TestReplaceNamespaceInDeploymentArgs(t *testing.T) {
 	manifest, err := mf.ManifestFrom(mf.Recursive(testData))
 	assertNoEror(t, err)
 
-	manifest, err = manifest.Transform(ReplaceNamespaceInDeploymentArgs("openshift-pipelines"))
+	manifest, err = manifest.Transform(ReplaceNamespaceInDeploymentArgs([]string{"tekton-results-watcher"}, "openshift-pipelines"))
 	assertNoEror(t, err)
 
 	d := &appsv1.Deployment{}
diff --git a/pkg/reconciler/kubernetes/tektondashboard/testdata/test-dashboard-transformer-updated.yaml b/pkg/reconciler/kubernetes/tektondashboard/testdata/test-dashboard-transformer-updated.yaml
index c3f5ebe53..1c6e7562e 100644
--- a/pkg/reconciler/kubernetes/tektondashboard/testdata/test-dashboard-transformer-updated.yaml
+++ b/pkg/reconciler/kubernetes/tektondashboard/testdata/test-dashboard-transformer-updated.yaml
@@ -404,15 +404,15 @@ spec:
         app.kubernetes.io/name: dashboard
         app.kubernetes.io/part-of: tekton-dashboard
         app.kubernetes.io/version: v0.48.0
-        operator.tekton.dev/deployment-spec-applied-hash: 0d7e625dccc2efab4493c409bdc71e88
+        operator.tekton.dev/deployment-spec-applied-hash: e521024acd6c6a280dc53fb4de0a6725
       name: tekton-dashboard
     spec:
       containers:
         - args:
             - --port=9097
             - --logout-url=
-            - --pipelines-namespace=tekton-pipelines
-            - --triggers-namespace=tekton-pipelines
+            - --pipelines-namespace=foo-ns
+            - --triggers-namespace=foo-ns
             - --read-only=false
             - --log-level=info
             - --log-format=json