Repository: jenkins-x/lighthouse (go)

Issue:
support other dashboard web applications / paths for tekton engine
right now the tekton engine is hard coded to only work with tekton dashboard using a single page URL:
https://github.com/jenkins-x/lighthouse/blob/master/pkg/engines/tekton/controller.go#L137

it would be nice to support an optional `dashboard-template` expression  in addition to the `dashboard-url` so that folks can link to other web UIs and consoles as well - e.g. https://github.com/jenkins-x/jx-pipelines-visualizer

Pull request:
fix: support other UIs than just tekton dashboard for reporting
fixes #1105

Patch:
diff --git a/charts/lighthouse/README.md b/charts/lighthouse/README.md
index ff5a832c..3e5eb31d 100644
--- a/charts/lighthouse/README.md
+++ b/charts/lighthouse/README.md
@@ -107,7 +107,8 @@ helm uninstall my-lighthouse --namespace lighthouse
 | `logFormat` | string | Log format | `"json"` |
 | `oauthToken` | string | Git token (used when GitHub app authentication is not enabled) | `""` |
 | `tektoncontroller.affinity` | object | [Affinity rules](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) applied to the tekton controller pods | `{}` |
-| `tektoncontroller.dashboardURL` | string | Tekton dashboard URL | `""` |
+| `tektoncontroller.dashboardTemplate` | string | Go template expression for URLs in the dashboard if not using Tekton dashboard | `""` |
+| `tektoncontroller.dashboardURL` | string | the dashboard URL (e.g. Tekton dashboard) | `""` |
 | `tektoncontroller.image.pullPolicy` | string | Template for computing the tekton controller docker image pull policy | `"{{ .Values.image.pullPolicy }}"` |
 | `tektoncontroller.image.repository` | string | Template for computing the tekton controller docker image repository | `"{{ .Values.image.parentRepository }}/lighthouse-tekton-controller"` |
 | `tektoncontroller.image.tag` | string | Template for computing the tekton controller docker image tag | `"{{ .Values.image.tag }}"` |
diff --git a/charts/lighthouse/templates/tekton-controller-deployment.yaml b/charts/lighthouse/templates/tekton-controller-deployment.yaml
index 5d6dcc22..ffffd372 100644
--- a/charts/lighthouse/templates/tekton-controller-deployment.yaml
+++ b/charts/lighthouse/templates/tekton-controller-deployment.yaml
@@ -29,6 +29,7 @@ spec:
         args:
           - --namespace={{ .Release.Namespace }}
           - --dashboard-url={{ .Values.tektoncontroller.dashboardURL }}
+          - --dashboard-template={{ .Values.tektoncontroller.dashboardTemplate }}
         ports:
           - name: metrics
             containerPort: 8080
diff --git a/charts/lighthouse/values.yaml b/charts/lighthouse/values.yaml
index 058039f7..04cb78e7 100644
--- a/charts/lighthouse/values.yaml
+++ b/charts/lighthouse/values.yaml
@@ -177,8 +177,10 @@ foghorn:
       memory: 128Mi
 
 tektoncontroller:
-  # tektoncontroller.dashboardURL -- Tekton dashboard URL
+  # tektoncontroller.dashboardURL -- the dashboard URL (e.g. Tekton dashboard)
   dashboardURL: ''
+  # tektoncontroller.dashboardTemplate -- Go template expression for URLs in the dashboard if not using Tekton dashboard
+  dashboardTemplate: ''
 
   # tektoncontroller.replicaCount -- Number of replicas
   replicaCount: 1
diff --git a/cmd/tektoncontroller/main.go b/cmd/tektoncontroller/main.go
index f3b8f100..e3f2c8de 100644
--- a/cmd/tektoncontroller/main.go
+++ b/cmd/tektoncontroller/main.go
@@ -16,8 +16,9 @@ import (
 )
 
 type options struct {
-	namespace    string
-	dashboardURL string
+	namespace         string
+	dashboardURL      string
+	dashboardTemplate string
 }
 
 func (o *options) Validate() error {
@@ -28,6 +29,7 @@ func gatherOptions(fs *flag.FlagSet, args ...string) options {
 	var o options
 	fs.StringVar(&o.namespace, "namespace", "", "The namespace to listen in")
 	fs.StringVar(&o.dashboardURL, "dashboard-url", "", "The base URL for the Tekton Dashboard to link to for build reports")
+	fs.StringVar(&o.dashboardTemplate, "dashboard-template", "", "The template expression for generating the URL to the build report based on the PipelineRun parameters. If not specified defaults to $LIGHTHOUSE_DASHBOARD_TEMPLATE")
 	err := fs.Parse(args)
 	if err != nil {
 		logrus.WithError(err).Fatal("Invalid options")
@@ -62,7 +64,7 @@ func main() {
 		logrus.WithError(err).Fatal("Unable to start manager")
 	}
 
-	reconciler := tektonengine.NewLighthouseJobReconciler(mgr.GetClient(), mgr.GetAPIReader(), mgr.GetScheme(), o.dashboardURL, o.namespace)
+	reconciler := tektonengine.NewLighthouseJobReconciler(mgr.GetClient(), mgr.GetAPIReader(), mgr.GetScheme(), o.dashboardURL, o.dashboardTemplate, o.namespace)
 	if err = reconciler.SetupWithManager(mgr); err != nil {
 		logrus.WithError(err).Fatal("Unable to create controller")
 	}
diff --git a/pkg/engines/tekton/controller.go b/pkg/engines/tekton/controller.go
index 91a727ae..b814e3f9 100644
--- a/pkg/engines/tekton/controller.go
+++ b/pkg/engines/tekton/controller.go
@@ -1,8 +1,11 @@
 package tekton
 
 import (
+	"bytes"
 	"context"
 	"fmt"
+	"os"
+	"text/template"
 
 	lighthousev1alpha1 "github.com/jenkins-x/lighthouse/pkg/apis/lighthouse/v1alpha1"
 	configjob "github.com/jenkins-x/lighthouse/pkg/config/job"
@@ -22,25 +25,30 @@ var apiGVStr = lighthousev1alpha1.SchemeGroupVersion.String()
 
 // LighthouseJobReconciler reconciles a LighthouseJob object
 type LighthouseJobReconciler struct {
-	client       client.Client
-	apiReader    client.Reader
-	logger       *logrus.Entry
-	scheme       *runtime.Scheme
-	idGenerator  buildIDGenerator
-	dashboardURL string
-	namespace    string
+	client            client.Client
+	apiReader         client.Reader
+	logger            *logrus.Entry
+	scheme            *runtime.Scheme
+	idGenerator       buildIDGenerator
+	dashboardURL      string
+	dashboardTemplate string
+	namespace         string
 }
 
 // NewLighthouseJobReconciler creates a LighthouseJob reconciler
-func NewLighthouseJobReconciler(client client.Client, apiReader client.Reader, scheme *runtime.Scheme, dashboardURL string, namespace string) *LighthouseJobReconciler {
+func NewLighthouseJobReconciler(client client.Client, apiReader client.Reader, scheme *runtime.Scheme, dashboardURL string, dashboardTemplate string, namespace string) *LighthouseJobReconciler {
+	if dashboardTemplate == "" {
+		dashboardTemplate = os.Getenv("LIGHTHOUSE_DASHBOARD_TEMPLATE")
+	}
 	return &LighthouseJobReconciler{
-		client:       client,
-		apiReader:    apiReader,
-		logger:       logrus.NewEntry(logrus.StandardLogger()).WithField("controller", controllerName),
-		scheme:       scheme,
-		dashboardURL: dashboardURL,
-		namespace:    namespace,
-		idGenerator:  &epochBuildIDGenerator{},
+		client:            client,
+		apiReader:         apiReader,
+		logger:            logrus.NewEntry(logrus.StandardLogger()).WithField("controller", controllerName),
+		scheme:            scheme,
+		dashboardURL:      dashboardURL,
+		dashboardTemplate: dashboardTemplate,
+		namespace:         namespace,
+		idGenerator:       &epochBuildIDGenerator{},
 	}
 }
 
@@ -134,7 +142,7 @@ func (r *LighthouseJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
 			return ctrl.Result{}, err
 		}
 		if r.dashboardURL != "" {
-			job.Status.ReportURL = fmt.Sprintf("%s/#/namespaces/%s/pipelineruns/%s", trimDashboardURL(r.dashboardURL), r.namespace, pipelineRun.Name)
+			job.Status.ReportURL = r.getPipelingetPipelineTargetURLeTargetURL(pipelineRun)
 		}
 		job.Status.Activity = ConvertPipelineRun(&pipelineRun)
 		if err := r.client.Status().Update(ctx, &job); err != nil {
@@ -147,3 +155,41 @@ func (r *LighthouseJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
 
 	return ctrl.Result{}, nil
 }
+
+func (r *LighthouseJobReconciler) getPipelingetPipelineTargetURLeTargetURL(pipelineRun pipelinev1beta1.PipelineRun) string {
+	if r.dashboardTemplate == "" {
+		return fmt.Sprintf("%s/#/namespaces/%s/pipelineruns/%s", trimDashboardURL(r.dashboardURL), r.namespace, pipelineRun.Name)
+	}
+	funcMap := map[string]interface{}{}
+	tmpl, err := template.New("value.gotmpl").Option("missingkey=error").Funcs(funcMap).Parse(r.dashboardTemplate)
+	if err != nil {
+		r.logger.WithError(err).Warnf("failed to parse template: %s", r.dashboardTemplate)
+		return ""
+	}
+
+	labels := pipelineRun.Labels
+	if labels == nil {
+		labels = map[string]string{}
+	}
+	namespace := pipelineRun.Namespace
+	if namespace == "" {
+		namespace = r.namespace
+	}
+	templateData := map[string]interface{}{
+		"Branch":      labels[util.BranchLabel],
+		"BuildNum":    labels[util.BuildNumLabel],
+		"Context":     labels[util.ContextLabel],
+		"Namespace":   namespace,
+		"Org":         labels[util.OrgLabel],
+		"PipelineRun": pipelineRun.Name,
+		"Pull":        labels[util.PullLabel],
+		"Repo":        labels[util.RepoLabel],
+	}
+	var buf bytes.Buffer
+	err = tmpl.Execute(&buf, templateData)
+	if err != nil {
+		r.logger.WithError(err).Warnf("failed to parse template: %s for PipelineRun %s", r.dashboardTemplate, pipelineRun.Name)
+		return ""
+	}
+	return fmt.Sprintf("%s/%s", trimDashboardURL(r.dashboardURL), buf.String())
+}


Test patch:
diff --git a/pkg/engines/tekton/controller_test.go b/pkg/engines/tekton/controller_test.go
index a19cceea..f14e2542 100644
--- a/pkg/engines/tekton/controller_test.go
+++ b/pkg/engines/tekton/controller_test.go
@@ -26,7 +26,8 @@ import (
 )
 
 const (
-	dashboardBaseURL = "https://example.com/"
+	dashboardBaseURL  = "https://example.com/"
+	dashboardTemplate = "#/namespaces/{{ .Namespace }}/pipelineruns/{{ .PipelineRun }}"
 )
 
 type seededRandIDGenerator struct{}
@@ -82,7 +83,7 @@ func TestReconcile(t *testing.T) {
 			err = pipelinev1beta1.AddToScheme(scheme)
 			assert.NoError(t, err)
 			c := fake.NewFakeClientWithScheme(scheme, state...)
-			reconciler := NewLighthouseJobReconciler(c, c, scheme, dashboardBaseURL, ns)
+			reconciler := NewLighthouseJobReconciler(c, c, scheme, dashboardBaseURL, dashboardTemplate, ns)
 			reconciler.idGenerator = &seededRandIDGenerator{}
 
 			// invoke reconcile