Repository: tektoncd/operator
Pull request #159 (2020-09-29 19:23:38)

Add API and Reconciler for dashboard
# Changes

* Add API and Reconciler for dashboard

/cc @nikhil-thomas 
<!-- 🎉🎉🎉 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)
- [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
NONE
```
<!--
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 **sthaha**
You can assign the PR to them by writing `/assign @sthaha` 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":["sthaha"]} -->
tekton-robot: @vdemeester: ![cat image](https://cdn2.thecatapi.com/images/avb.jpg)

<details>

In response to [this](https://github.com/tektoncd/operator/pull/159#pullrequestreview-499184641):

>/meow


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>
tekton-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/tektoncd/operator/pull/159#pullrequestreview-499184641" title="Approved">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)~~ [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: @savitaashture: 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>
tekton-robot: The following is the coverage report on the affected files.
Say `/test pull-tekton-operator-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/reconciler/common/releases.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/159/pull-tekton-operator-go-coverage/1316733123538456576/artifacts/line-cov.html#file1) | 66.7% | 65.5% | -1.2

nikhil-thomas: /lgtm
/approve
tekton-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/tektoncd/operator/pull/159#issuecomment-709334007" title="Approved">nikhil-thomas</a>*, *<a href="https://github.com/tektoncd/operator/pull/159#pullrequestreview-499184641" title="Approved">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":[]} -->

Patch:
diff --git a/cmd/kubernetes/kodata/tekton-dashboard/0.8.2/tekton-dashboard-release.yaml b/cmd/kubernetes/kodata/tekton-dashboard/0.8.2/tekton-dashboard-release.yaml
new file mode 100644
index 000000000..0d419b8d4
--- /dev/null
+++ b/cmd/kubernetes/kodata/tekton-dashboard/0.8.2/tekton-dashboard-release.yaml
@@ -0,0 +1,516 @@
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: extensions.dashboard.tekton.dev
+spec:
+  additionalPrinterColumns:
+  - JSONPath: .spec.apiVersion
+    name: API version
+    type: string
+  - JSONPath: .spec.name
+    name: Kind
+    type: string
+  - JSONPath: .spec.displayname
+    name: Display name
+    type: string
+  - JSONPath: .metadata.creationTimestamp
+    name: Age
+    type: date
+  group: dashboard.tekton.dev
+  names:
+    categories:
+    - tekton
+    - tekton-dashboard
+    kind: Extension
+    plural: extensions
+    shortNames:
+    - ext
+    - exts
+  preserveUnknownFields: false
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      type: object
+      x-kubernetes-preserve-unknown-fields: true
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard
+  namespace: tekton-pipelines
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-backend
+rules:
+- apiGroups:
+  - apiextensions.k8s.io
+  resources:
+  - customresourcedefinitions
+  verbs:
+  - get
+  - list
+- apiGroups:
+  - security.openshift.io
+  resources:
+  - securitycontextconstraints
+  verbs:
+  - use
+- apiGroups:
+  - tekton.dev
+  resources:
+  - clustertasks
+  - clustertasks/status
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - triggers.tekton.dev
+  resources:
+  - clustertriggerbindings
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - dashboard.tekton.dev
+  resources:
+  - extensions
+  verbs:
+  - create
+  - update
+  - delete
+  - patch
+- apiGroups:
+  - tekton.dev
+  resources:
+  - clustertasks
+  - clustertasks/status
+  verbs:
+  - create
+  - update
+  - delete
+  - patch
+- apiGroups:
+  - triggers.tekton.dev
+  resources:
+  - clustertriggerbindings
+  verbs:
+  - create
+  - update
+  - delete
+  - patch
+  - add
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-dashboard
+rules:
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - list
+- apiGroups:
+  - route.openshift.io
+  resources:
+  - routes
+  verbs:
+  - get
+  - list
+- apiGroups:
+  - extensions
+  - apps
+  resources:
+  - ingresses
+  verbs:
+  - get
+  - list
+  - watch
+---
+aggregationRule:
+  clusterRoleSelectors:
+  - matchLabels:
+      rbac.dashboard.tekton.dev/aggregate-to-dashboard: "true"
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-extensions
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-pipelines
+rules:
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-tenant
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - services
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - dashboard.tekton.dev
+  resources:
+  - extensions
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - serviceaccounts
+  - pods/log
+  - namespaces
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - tekton.dev
+  resources:
+  - tasks
+  - taskruns
+  - pipelines
+  - pipelineruns
+  - pipelineresources
+  - conditions
+  - tasks/status
+  - taskruns/status
+  - pipelines/status
+  - pipelineruns/status
+  - taskruns/finalizers
+  - pipelineruns/finalizers
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - triggers.tekton.dev
+  resources:
+  - eventlisteners
+  - triggerbindings
+  - triggertemplates
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - serviceaccounts
+  verbs:
+  - update
+  - patch
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
+  - create
+  - update
+  - delete
+- apiGroups:
+  - tekton.dev
+  resources:
+  - tasks
+  - taskruns
+  - pipelines
+  - pipelineruns
+  - pipelineresources
+  - conditions
+  - taskruns/finalizers
+  - pipelineruns/finalizers
+  - tasks/status
+  - taskruns/status
+  - pipelines/status
+  - pipelineruns/status
+  verbs:
+  - create
+  - update
+  - delete
+  - patch
+- apiGroups:
+  - triggers.tekton.dev
+  resources:
+  - eventlisteners
+  - triggerbindings
+  - triggertemplates
+  verbs:
+  - create
+  - update
+  - delete
+  - patch
+  - add
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-triggers
+rules:
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-backend
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-dashboard-backend
+subjects:
+- kind: ServiceAccount
+  name: tekton-dashboard
+  namespace: tekton-pipelines
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: tekton-dashboard
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/name: dashboard
+    app.kubernetes.io/part-of: tekton-dashboard
+    app.kubernetes.io/version: v0.8.2
+    dashboard.tekton.dev/release: v0.8.2
+    version: v0.8.2
+  name: tekton-dashboard
+  namespace: tekton-pipelines
+spec:
+  ports:
+  - name: http
+    port: 9097
+    protocol: TCP
+    targetPort: 9097
+  selector:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/name: dashboard
+    app.kubernetes.io/part-of: tekton-dashboard
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: tekton-dashboard
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/name: dashboard
+    app.kubernetes.io/part-of: tekton-dashboard
+    app.kubernetes.io/version: v0.8.2
+    dashboard.tekton.dev/release: v0.8.2
+    version: v0.8.2
+  name: tekton-dashboard
+  namespace: tekton-pipelines
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/component: dashboard
+      app.kubernetes.io/instance: default
+      app.kubernetes.io/name: dashboard
+      app.kubernetes.io/part-of: tekton-dashboard
+  template:
+    metadata:
+      labels:
+        app: tekton-dashboard
+        app.kubernetes.io/component: dashboard
+        app.kubernetes.io/instance: default
+        app.kubernetes.io/name: dashboard
+        app.kubernetes.io/part-of: tekton-dashboard
+        app.kubernetes.io/version: v0.8.2
+      name: tekton-dashboard
+    spec:
+      containers:
+      - args:
+        - --port=9097
+        - --logout-url=
+        - --pipelines-namespace=tekton-pipelines
+        - --triggers-namespace=tekton-pipelines
+        - --read-only=false
+        - --csrf-secure-cookie=false
+        - --log-level=info
+        - --log-format=json
+        - --namespace=
+        - --openshift=false
+        - --stream-logs=false
+        env:
+        - name: INSTALLED_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        image: gcr.io/tekton-releases/github.com/tektoncd/dashboard/cmd/dashboard@sha256:4175d6237de3f85d4cfa2e331e244f0b1c20776455af10dcccdb81a3f9ebb012
+        livenessProbe:
+          httpGet:
+            path: /health
+            port: 9097
+        name: tekton-dashboard
+        ports:
+        - containerPort: 9097
+        readinessProbe:
+          httpGet:
+            path: /readiness
+            port: 9097
+      securityContext:
+        runAsNonRoot: true
+      serviceAccountName: tekton-dashboard
+      volumes: []
+
+---
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-pipelines
+  namespace: tekton-pipelines
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-dashboard-pipelines
+subjects:
+  - kind: ServiceAccount
+    name: tekton-dashboard
+    namespace: tekton-pipelines
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-dashboard
+  namespace: tekton-pipelines
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-dashboard-dashboard
+subjects:
+  - kind: ServiceAccount
+    name: tekton-dashboard
+    namespace: tekton-pipelines
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-triggers
+  namespace: tekton-pipelines
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-dashboard-triggers
+subjects:
+  - kind: ServiceAccount
+    name: tekton-dashboard
+    namespace: tekton-pipelines
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-tenant
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-dashboard-tenant
+subjects:
+  - kind: ServiceAccount
+    name: tekton-dashboard
+    namespace: tekton-pipelines
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: dashboard
+    app.kubernetes.io/instance: default
+    app.kubernetes.io/part-of: tekton-dashboard
+  name: tekton-dashboard-extensions
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-dashboard-extensions
+subjects:
+  - kind: ServiceAccount
+    name: tekton-dashboard
+    namespace: tekton-pipelines
diff --git a/cmd/kubernetes/main.go b/cmd/kubernetes/main.go
index 0955b27d0..8489b405e 100644
--- a/cmd/kubernetes/main.go
+++ b/cmd/kubernetes/main.go
@@ -17,6 +17,7 @@ limitations under the License.
 package main
 
 import (
+	"github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektondashboard"
 	"github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektonpipeline"
 	"github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektontrigger"
 	"knative.dev/pkg/injection/sharedmain"
@@ -26,5 +27,6 @@ func main() {
 	sharedmain.Main("tekton-operator",
 		tektonpipeline.NewController,
 		tektontrigger.NewController,
+		tektondashboard.NewController,
 	)
 }
diff --git a/config/300-operator_v1alpha1_dashboard_crd.yaml b/config/300-operator_v1alpha1_dashboard_crd.yaml
new file mode 100644
index 000000000..a5d71a2bd
--- /dev/null
+++ b/config/300-operator_v1alpha1_dashboard_crd.yaml
@@ -0,0 +1,119 @@
+# Copyright 2020 The Tekton Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: tektondashboards.operator.tekton.dev
+  labels:
+    version: "devel"
+    operator.tekton.dev/release: "devel"
+spec:
+  additionalPrinterColumns:
+    - JSONPath: .status.version
+      name: Version
+      type: string
+    - JSONPath: .status.conditions[?(@.type=="Ready")].status
+      name: Ready
+      type: string
+    - JSONPath: ".status.conditions[?(@.type==\"Ready\")].reason"
+      name: Reason
+      type: string
+  group: operator.tekton.dev
+  names:
+    kind: TektonDashboard
+    listKind: TektonDashboardList
+    plural: tektondashboards
+    singular: tektondashboard
+  preserveUnknownFields: false
+  scope: Cluster
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      type: object
+      description: Schema for the tektondashboards API
+      properties:
+        apiVersion:
+          description: 'APIVersion defines the versioned schema of this representation
+            of an object. Servers should convert recognized schemas to the latest
+            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
+          type: string
+        kind:
+          description: 'Kind is a string value representing the REST resource this
+            object represents. Servers may infer this from the endpoint the client
+            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
+          type: string
+        metadata:
+          type: object
+        spec:
+          description: Spec defines the desired state of TektonDashboard
+          properties:
+            targetNamespace:
+              description: namespace where tekton dashboard will be installed
+              type: string
+          type: object
+        status:
+          description: Status defines the observed state of TektonDashboard
+          properties:
+            observedGeneration:
+              description: The generation last processed by the controller
+              type: integer
+            conditions:
+              description: The latest available observations of a resource's current
+                state.
+              items:
+                properties:
+                  lastTransitionTime:
+                    description: LastTransitionTime is the last time the condition
+                      transitioned from one status to another. We use VolatileTime
+                      in place of metav1.Time to exclude this from creating equality.Semantic
+                      differences (all other things held constant).
+                    type: string
+                  message:
+                    description: A human readable message indicating details about
+                      the transition.
+                    type: string
+                  reason:
+                    description: The reason for the condition's last transition.
+                    type: string
+                  severity:
+                    description: Severity with which to treat failures of this type
+                      of condition. When this is not specified, it defaults to Error.
+                    type: string
+                  status:
+                    description: Status of the condition, one of True, False, Unknown.
+                    type: string
+                  type:
+                    description: Type of condition.
+                    type: string
+                required:
+                  - type
+                  - status
+                type: object
+              type: array
+            version:
+              description: The version of the installed release
+              type: string
+            manifests:
+              description: The list of serving manifests, which have been installed by the operator
+              type: array
+              items:
+                type: string
+          type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
diff --git a/config/crs/operator_v1alpha1_dashboard_cr.yaml b/config/crs/operator_v1alpha1_dashboard_cr.yaml
new file mode 100644
index 000000000..9fd5d3731
--- /dev/null
+++ b/config/crs/operator_v1alpha1_dashboard_cr.yaml
@@ -0,0 +1,6 @@
+apiVersion: operator.tekton.dev/v1alpha1
+kind: TektonDashboard
+metadata:
+  name: dashboard
+spec:
+  targetNamespace: tekton-pipelines
diff --git a/pkg/apis/operator/v1alpha1/register.go b/pkg/apis/operator/v1alpha1/register.go
index a8a08a29a..018bc2f0e 100644
--- a/pkg/apis/operator/v1alpha1/register.go
+++ b/pkg/apis/operator/v1alpha1/register.go
@@ -34,6 +34,9 @@ const (
 
 	// KindTektonTrigger is the Kind of Tekton Trigger in a GVK context.
 	KindTektonTrigger = "TektonTrigger"
+
+	// KindTektonDashboard is the Kind of Tekton Dashboard in a GVK context.
+	KindTektonDashboard = "TektonDashboard"
 )
 
 // Resource takes an unqualified resource and returns a Group qualified GroupResource
@@ -49,6 +52,8 @@ func addKnownTypes(s *runtime.Scheme) error {
 		&TektonPipelineList{},
 		&TektonTrigger{},
 		&TektonTriggerList{},
+		&TektonDashboard{},
+		&TektonDashboardList{},
 	)
 	metav1.AddToGroupVersion(s, SchemeGroupVersion)
 	return nil
diff --git a/pkg/apis/operator/v1alpha1/tektondashboard_lifecycle.go b/pkg/apis/operator/v1alpha1/tektondashboard_lifecycle.go
new file mode 100644
index 000000000..7d6c45a4a
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/tektondashboard_lifecycle.go
@@ -0,0 +1,127 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"knative.dev/pkg/apis"
+)
+
+var (
+	_ TektonComponentStatus = (*TektonDashboardStatus)(nil)
+
+	dashboardCondSet = apis.NewLivingConditionSet(
+		DependenciesInstalled,
+		DeploymentsAvailable,
+		InstallSucceeded,
+	)
+)
+
+// GroupVersionKind returns SchemeGroupVersion of a TektonDashboard
+func (tp *TektonDashboard) GroupVersionKind() schema.GroupVersionKind {
+	return SchemeGroupVersion.WithKind(KindTektonDashboard)
+}
+
+// GetCondition returns the current condition of a given condition type
+func (tps *TektonDashboardStatus) GetCondition(t apis.ConditionType) *apis.Condition {
+	return dashboardCondSet.Manage(tps).GetCondition(t)
+}
+
+// InitializeConditions initializes conditions of an TektonDashboardStatus
+func (tps *TektonDashboardStatus) InitializeConditions() {
+	dashboardCondSet.Manage(tps).InitializeConditions()
+}
+
+// IsReady looks at the conditions returns true if they are all true.
+func (tps *TektonDashboardStatus) IsReady() bool {
+	return dashboardCondSet.Manage(tps).IsHappy()
+}
+
+// MarkInstallSucceeded marks the InstallationSucceeded status as true.
+func (tps *TektonDashboardStatus) MarkInstallSucceeded() {
+	dashboardCondSet.Manage(tps).MarkTrue(InstallSucceeded)
+	if tps.GetCondition(DependenciesInstalled).IsUnknown() {
+		// Assume deps are installed if we're not sure
+		tps.MarkDependenciesInstalled()
+	}
+}
+
+// MarkInstallFailed marks the InstallationSucceeded status as false with the given
+// message.
+func (tps *TektonDashboardStatus) MarkInstallFailed(msg string) {
+	dashboardCondSet.Manage(tps).MarkFalse(
+		InstallSucceeded,
+		"Error",
+		"Install failed with message: %s", msg)
+}
+
+// MarkDeploymentsAvailable marks the DeploymentsAvailable status as true.
+func (tps *TektonDashboardStatus) MarkDeploymentsAvailable() {
+	dashboardCondSet.Manage(tps).MarkTrue(DeploymentsAvailable)
+}
+
+// MarkDeploymentsNotReady marks the DeploymentsAvailable status as false and calls out
+// it's waiting for deployments.
+func (tps *TektonDashboardStatus) MarkDeploymentsNotReady() {
+	dashboardCondSet.Manage(tps).MarkFalse(
+		DeploymentsAvailable,
+		"NotReady",
+		"Waiting on deployments")
+}
+
+// MarkDependenciesInstalled marks the DependenciesInstalled status as true.
+func (tps *TektonDashboardStatus) MarkDependenciesInstalled() {
+	dashboardCondSet.Manage(tps).MarkTrue(DependenciesInstalled)
+}
+
+// MarkDependencyInstalling marks the DependenciesInstalled status as false with the
+// given message.
+func (tps *TektonDashboardStatus) MarkDependencyInstalling(msg string) {
+	dashboardCondSet.Manage(tps).MarkFalse(
+		DependenciesInstalled,
+		"Installing",
+		"Dependency installing: %s", msg)
+}
+
+// MarkDependencyMissing marks the DependenciesInstalled status as false with the
+// given message.
+func (tps *TektonDashboardStatus) MarkDependencyMissing(msg string) {
+	dashboardCondSet.Manage(tps).MarkFalse(
+		DependenciesInstalled,
+		"Error",
+		"Dependency missing: %s", msg)
+}
+
+// GetVersion gets the currently installed version of the component.
+func (tps *TektonDashboardStatus) GetVersion() string {
+	return tps.Version
+}
+
+// SetVersion sets the currently installed version of the component.
+func (tps *TektonDashboardStatus) SetVersion(version string) {
+	tps.Version = version
+}
+
+// GetManifests gets the url links of the manifests.
+func (tps *TektonDashboardStatus) GetManifests() []string {
+	return tps.Manifests
+}
+
+// SetVersion sets the url links of the manifests.
+func (tps *TektonDashboardStatus) SetManifests(manifests []string) {
+	tps.Manifests = manifests
+}
diff --git a/pkg/apis/operator/v1alpha1/tektondashboard_types.go b/pkg/apis/operator/v1alpha1/tektondashboard_types.go
new file mode 100644
index 000000000..c12dbf009
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/tektondashboard_types.go
@@ -0,0 +1,76 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	duckv1 "knative.dev/pkg/apis/duck/v1"
+)
+
+var (
+	_ TektonComponent     = (*TektonDashboard)(nil)
+	_ TektonComponentSpec = (*TektonDashboardSpec)(nil)
+)
+
+// TektonDashboard is the Schema for the tektondashboards API
+// +genclient
+// +genreconciler:krshapedlogic=false
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +genclient:nonNamespaced
+type TektonDashboard struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   TektonDashboardSpec   `json:"spec,omitempty"`
+	Status TektonDashboardStatus `json:"status,omitempty"`
+}
+
+// GetSpec implements TektonComponent
+func (tp *TektonDashboard) GetSpec() TektonComponentSpec {
+	return &tp.Spec
+}
+
+// GetStatus implements TektonComponent
+func (tp *TektonDashboard) GetStatus() TektonComponentStatus {
+	return &tp.Status
+}
+
+// TektonDashboardSpec defines the desired state of TektonDashboard
+type TektonDashboardSpec struct {
+	CommonSpec `json:",inline"`
+}
+
+// TektonDashboardStatus defines the observed state of TektonDashboard
+type TektonDashboardStatus struct {
+	duckv1.Status `json:",inline"`
+
+	// The version of the installed release
+	// +optional
+	Version string `json:"version,omitempty"`
+
+	// The url links of the manifests, separated by comma
+	// +optional
+	Manifests []string `json:"manifests,omitempty"`
+}
+
+// TektonDashboardsList contains a list of TektonDashboard
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+type TektonDashboardList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []TektonDashboard `json:"items"`
+}
diff --git a/pkg/apis/operator/v1alpha1/tektontrigger_types.go b/pkg/apis/operator/v1alpha1/tektontrigger_types.go
index e7470a86b..4f127cdb2 100644
--- a/pkg/apis/operator/v1alpha1/tektontrigger_types.go
+++ b/pkg/apis/operator/v1alpha1/tektontrigger_types.go
@@ -54,7 +54,7 @@ type TektonTriggerSpec struct {
 	CommonSpec `json:",inline"`
 }
 
-// TektonPipelineStatus defines the observed state of TektonTrigger
+// TektonTriggerStatus defines the observed state of TektonTrigger
 type TektonTriggerStatus struct {
 	duckv1.Status `json:",inline"`
 
diff --git a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
index 5a35d992f..3e860972c 100644
--- a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
@@ -40,6 +40,106 @@ func (in *CommonSpec) DeepCopy() *CommonSpec {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TektonDashboard) DeepCopyInto(out *TektonDashboard) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	out.Spec = in.Spec
+	in.Status.DeepCopyInto(&out.Status)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TektonDashboard.
+func (in *TektonDashboard) DeepCopy() *TektonDashboard {
+	if in == nil {
+		return nil
+	}
+	out := new(TektonDashboard)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TektonDashboard) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TektonDashboardList) DeepCopyInto(out *TektonDashboardList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]TektonDashboard, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TektonDashboardList.
+func (in *TektonDashboardList) DeepCopy() *TektonDashboardList {
+	if in == nil {
+		return nil
+	}
+	out := new(TektonDashboardList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TektonDashboardList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TektonDashboardSpec) DeepCopyInto(out *TektonDashboardSpec) {
+	*out = *in
+	out.CommonSpec = in.CommonSpec
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TektonDashboardSpec.
+func (in *TektonDashboardSpec) DeepCopy() *TektonDashboardSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(TektonDashboardSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TektonDashboardStatus) DeepCopyInto(out *TektonDashboardStatus) {
+	*out = *in
+	in.Status.DeepCopyInto(&out.Status)
+	if in.Manifests != nil {
+		in, out := &in.Manifests, &out.Manifests
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TektonDashboardStatus.
+func (in *TektonDashboardStatus) DeepCopy() *TektonDashboardStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(TektonDashboardStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TektonPipeline) DeepCopyInto(out *TektonPipeline) {
 	*out = *in
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go
index c8cbb2598..8db05ca89 100644
--- a/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go
@@ -28,6 +28,10 @@ type FakeOperatorV1alpha1 struct {
 	*testing.Fake
 }
 
+func (c *FakeOperatorV1alpha1) TektonDashboards() v1alpha1.TektonDashboardInterface {
+	return &FakeTektonDashboards{c}
+}
+
 func (c *FakeOperatorV1alpha1) TektonPipelines() v1alpha1.TektonPipelineInterface {
 	return &FakeTektonPipelines{c}
 }
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_tektondashboard.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_tektondashboard.go
new file mode 100644
index 000000000..687a4cd5a
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_tektondashboard.go
@@ -0,0 +1,131 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+	v1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	schema "k8s.io/apimachinery/pkg/runtime/schema"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	testing "k8s.io/client-go/testing"
+)
+
+// FakeTektonDashboards implements TektonDashboardInterface
+type FakeTektonDashboards struct {
+	Fake *FakeOperatorV1alpha1
+}
+
+var tektondashboardsResource = schema.GroupVersionResource{Group: "operator.tekton.dev", Version: "v1alpha1", Resource: "tektondashboards"}
+
+var tektondashboardsKind = schema.GroupVersionKind{Group: "operator.tekton.dev", Version: "v1alpha1", Kind: "TektonDashboard"}
+
+// Get takes name of the tektonDashboard, and returns the corresponding tektonDashboard object, and an error if there is any.
+func (c *FakeTektonDashboards) Get(name string, options v1.GetOptions) (result *v1alpha1.TektonDashboard, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootGetAction(tektondashboardsResource, name), &v1alpha1.TektonDashboard{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TektonDashboard), err
+}
+
+// List takes label and field selectors, and returns the list of TektonDashboards that match those selectors.
+func (c *FakeTektonDashboards) List(opts v1.ListOptions) (result *v1alpha1.TektonDashboardList, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootListAction(tektondashboardsResource, tektondashboardsKind, opts), &v1alpha1.TektonDashboardList{})
+	if obj == nil {
+		return nil, err
+	}
+
+	label, _, _ := testing.ExtractFromListOptions(opts)
+	if label == nil {
+		label = labels.Everything()
+	}
+	list := &v1alpha1.TektonDashboardList{ListMeta: obj.(*v1alpha1.TektonDashboardList).ListMeta}
+	for _, item := range obj.(*v1alpha1.TektonDashboardList).Items {
+		if label.Matches(labels.Set(item.Labels)) {
+			list.Items = append(list.Items, item)
+		}
+	}
+	return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested tektonDashboards.
+func (c *FakeTektonDashboards) Watch(opts v1.ListOptions) (watch.Interface, error) {
+	return c.Fake.
+		InvokesWatch(testing.NewRootWatchAction(tektondashboardsResource, opts))
+}
+
+// Create takes the representation of a tektonDashboard and creates it.  Returns the server's representation of the tektonDashboard, and an error, if there is any.
+func (c *FakeTektonDashboards) Create(tektonDashboard *v1alpha1.TektonDashboard) (result *v1alpha1.TektonDashboard, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootCreateAction(tektondashboardsResource, tektonDashboard), &v1alpha1.TektonDashboard{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TektonDashboard), err
+}
+
+// Update takes the representation of a tektonDashboard and updates it. Returns the server's representation of the tektonDashboard, and an error, if there is any.
+func (c *FakeTektonDashboards) Update(tektonDashboard *v1alpha1.TektonDashboard) (result *v1alpha1.TektonDashboard, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootUpdateAction(tektondashboardsResource, tektonDashboard), &v1alpha1.TektonDashboard{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TektonDashboard), err
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *FakeTektonDashboards) UpdateStatus(tektonDashboard *v1alpha1.TektonDashboard) (*v1alpha1.TektonDashboard, error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootUpdateSubresourceAction(tektondashboardsResource, "status", tektonDashboard), &v1alpha1.TektonDashboard{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TektonDashboard), err
+}
+
+// Delete takes name of the tektonDashboard and deletes it. Returns an error if one occurs.
+func (c *FakeTektonDashboards) Delete(name string, options *v1.DeleteOptions) error {
+	_, err := c.Fake.
+		Invokes(testing.NewRootDeleteAction(tektondashboardsResource, name), &v1alpha1.TektonDashboard{})
+	return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeTektonDashboards) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
+	action := testing.NewRootDeleteCollectionAction(tektondashboardsResource, listOptions)
+
+	_, err := c.Fake.Invokes(action, &v1alpha1.TektonDashboardList{})
+	return err
+}
+
+// Patch applies the patch and returns the patched tektonDashboard.
+func (c *FakeTektonDashboards) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TektonDashboard, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewRootPatchSubresourceAction(tektondashboardsResource, name, pt, data, subresources...), &v1alpha1.TektonDashboard{})
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*v1alpha1.TektonDashboard), err
+}
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go
index dad89cb2a..95718d528 100644
--- a/pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go
@@ -18,6 +18,8 @@ limitations under the License.
 
 package v1alpha1
 
+type TektonDashboardExpansion interface{}
+
 type TektonPipelineExpansion interface{}
 
 type TektonTriggerExpansion interface{}
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go
index 35e7843a0..effd91ab9 100644
--- a/pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go
@@ -26,6 +26,7 @@ import (
 
 type OperatorV1alpha1Interface interface {
 	RESTClient() rest.Interface
+	TektonDashboardsGetter
 	TektonPipelinesGetter
 	TektonTriggersGetter
 }
@@ -35,6 +36,10 @@ type OperatorV1alpha1Client struct {
 	restClient rest.Interface
 }
 
+func (c *OperatorV1alpha1Client) TektonDashboards() TektonDashboardInterface {
+	return newTektonDashboards(c)
+}
+
 func (c *OperatorV1alpha1Client) TektonPipelines() TektonPipelineInterface {
 	return newTektonPipelines(c)
 }
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/tektondashboard.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/tektondashboard.go
new file mode 100644
index 000000000..82b4c7f49
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/tektondashboard.go
@@ -0,0 +1,180 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	"time"
+
+	v1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	scheme "github.com/tektoncd/operator/pkg/client/clientset/versioned/scheme"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	rest "k8s.io/client-go/rest"
+)
+
+// TektonDashboardsGetter has a method to return a TektonDashboardInterface.
+// A group's client should implement this interface.
+type TektonDashboardsGetter interface {
+	TektonDashboards() TektonDashboardInterface
+}
+
+// TektonDashboardInterface has methods to work with TektonDashboard resources.
+type TektonDashboardInterface interface {
+	Create(*v1alpha1.TektonDashboard) (*v1alpha1.TektonDashboard, error)
+	Update(*v1alpha1.TektonDashboard) (*v1alpha1.TektonDashboard, error)
+	UpdateStatus(*v1alpha1.TektonDashboard) (*v1alpha1.TektonDashboard, error)
+	Delete(name string, options *v1.DeleteOptions) error
+	DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
+	Get(name string, options v1.GetOptions) (*v1alpha1.TektonDashboard, error)
+	List(opts v1.ListOptions) (*v1alpha1.TektonDashboardList, error)
+	Watch(opts v1.ListOptions) (watch.Interface, error)
+	Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TektonDashboard, err error)
+	TektonDashboardExpansion
+}
+
+// tektonDashboards implements TektonDashboardInterface
+type tektonDashboards struct {
+	client rest.Interface
+}
+
+// newTektonDashboards returns a TektonDashboards
+func newTektonDashboards(c *OperatorV1alpha1Client) *tektonDashboards {
+	return &tektonDashboards{
+		client: c.RESTClient(),
+	}
+}
+
+// Get takes name of the tektonDashboard, and returns the corresponding tektonDashboard object, and an error if there is any.
+func (c *tektonDashboards) Get(name string, options v1.GetOptions) (result *v1alpha1.TektonDashboard, err error) {
+	result = &v1alpha1.TektonDashboard{}
+	err = c.client.Get().
+		Resource("tektondashboards").
+		Name(name).
+		VersionedParams(&options, scheme.ParameterCodec).
+		Do().
+		Into(result)
+	return
+}
+
+// List takes label and field selectors, and returns the list of TektonDashboards that match those selectors.
+func (c *tektonDashboards) List(opts v1.ListOptions) (result *v1alpha1.TektonDashboardList, err error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	result = &v1alpha1.TektonDashboardList{}
+	err = c.client.Get().
+		Resource("tektondashboards").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Do().
+		Into(result)
+	return
+}
+
+// Watch returns a watch.Interface that watches the requested tektonDashboards.
+func (c *tektonDashboards) Watch(opts v1.ListOptions) (watch.Interface, error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	opts.Watch = true
+	return c.client.Get().
+		Resource("tektondashboards").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Watch()
+}
+
+// Create takes the representation of a tektonDashboard and creates it.  Returns the server's representation of the tektonDashboard, and an error, if there is any.
+func (c *tektonDashboards) Create(tektonDashboard *v1alpha1.TektonDashboard) (result *v1alpha1.TektonDashboard, err error) {
+	result = &v1alpha1.TektonDashboard{}
+	err = c.client.Post().
+		Resource("tektondashboards").
+		Body(tektonDashboard).
+		Do().
+		Into(result)
+	return
+}
+
+// Update takes the representation of a tektonDashboard and updates it. Returns the server's representation of the tektonDashboard, and an error, if there is any.
+func (c *tektonDashboards) Update(tektonDashboard *v1alpha1.TektonDashboard) (result *v1alpha1.TektonDashboard, err error) {
+	result = &v1alpha1.TektonDashboard{}
+	err = c.client.Put().
+		Resource("tektondashboards").
+		Name(tektonDashboard.Name).
+		Body(tektonDashboard).
+		Do().
+		Into(result)
+	return
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+
+func (c *tektonDashboards) UpdateStatus(tektonDashboard *v1alpha1.TektonDashboard) (result *v1alpha1.TektonDashboard, err error) {
+	result = &v1alpha1.TektonDashboard{}
+	err = c.client.Put().
+		Resource("tektondashboards").
+		Name(tektonDashboard.Name).
+		SubResource("status").
+		Body(tektonDashboard).
+		Do().
+		Into(result)
+	return
+}
+
+// Delete takes name of the tektonDashboard and deletes it. Returns an error if one occurs.
+func (c *tektonDashboards) Delete(name string, options *v1.DeleteOptions) error {
+	return c.client.Delete().
+		Resource("tektondashboards").
+		Name(name).
+		Body(options).
+		Do().
+		Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *tektonDashboards) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
+	var timeout time.Duration
+	if listOptions.TimeoutSeconds != nil {
+		timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
+	}
+	return c.client.Delete().
+		Resource("tektondashboards").
+		VersionedParams(&listOptions, scheme.ParameterCodec).
+		Timeout(timeout).
+		Body(options).
+		Do().
+		Error()
+}
+
+// Patch applies the patch and returns the patched tektonDashboard.
+func (c *tektonDashboards) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TektonDashboard, err error) {
+	result = &v1alpha1.TektonDashboard{}
+	err = c.client.Patch(pt).
+		Resource("tektondashboards").
+		SubResource(subresources...).
+		Name(name).
+		Body(data).
+		Do().
+		Into(result)
+	return
+}
diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go
index f48a91503..c9d6d1026 100644
--- a/pkg/client/informers/externalversions/generic.go
+++ b/pkg/client/informers/externalversions/generic.go
@@ -53,6 +53,8 @@ func (f *genericInformer) Lister() cache.GenericLister {
 func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
 	switch resource {
 	// Group=operator.tekton.dev, Version=v1alpha1
+	case v1alpha1.SchemeGroupVersion.WithResource("tektondashboards"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().TektonDashboards().Informer()}, nil
 	case v1alpha1.SchemeGroupVersion.WithResource("tektonpipelines"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().TektonPipelines().Informer()}, nil
 	case v1alpha1.SchemeGroupVersion.WithResource("tektontriggers"):
diff --git a/pkg/client/informers/externalversions/operator/v1alpha1/interface.go b/pkg/client/informers/externalversions/operator/v1alpha1/interface.go
index 94dcb28a8..e9ac9d53a 100644
--- a/pkg/client/informers/externalversions/operator/v1alpha1/interface.go
+++ b/pkg/client/informers/externalversions/operator/v1alpha1/interface.go
@@ -24,6 +24,8 @@ import (
 
 // Interface provides access to all the informers in this group version.
 type Interface interface {
+	// TektonDashboards returns a TektonDashboardInformer.
+	TektonDashboards() TektonDashboardInformer
 	// TektonPipelines returns a TektonPipelineInformer.
 	TektonPipelines() TektonPipelineInformer
 	// TektonTriggers returns a TektonTriggerInformer.
@@ -41,6 +43,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
 	return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
 }
 
+// TektonDashboards returns a TektonDashboardInformer.
+func (v *version) TektonDashboards() TektonDashboardInformer {
+	return &tektonDashboardInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
+}
+
 // TektonPipelines returns a TektonPipelineInformer.
 func (v *version) TektonPipelines() TektonPipelineInformer {
 	return &tektonPipelineInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
diff --git a/pkg/client/informers/externalversions/operator/v1alpha1/tektondashboard.go b/pkg/client/informers/externalversions/operator/v1alpha1/tektondashboard.go
new file mode 100644
index 000000000..0c562d5e1
--- /dev/null
+++ b/pkg/client/informers/externalversions/operator/v1alpha1/tektondashboard.go
@@ -0,0 +1,88 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	time "time"
+
+	operatorv1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	versioned "github.com/tektoncd/operator/pkg/client/clientset/versioned"
+	internalinterfaces "github.com/tektoncd/operator/pkg/client/informers/externalversions/internalinterfaces"
+	v1alpha1 "github.com/tektoncd/operator/pkg/client/listers/operator/v1alpha1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	watch "k8s.io/apimachinery/pkg/watch"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// TektonDashboardInformer provides access to a shared informer and lister for
+// TektonDashboards.
+type TektonDashboardInformer interface {
+	Informer() cache.SharedIndexInformer
+	Lister() v1alpha1.TektonDashboardLister
+}
+
+type tektonDashboardInformer struct {
+	factory          internalinterfaces.SharedInformerFactory
+	tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// NewTektonDashboardInformer constructs a new informer for TektonDashboard 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 NewTektonDashboardInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredTektonDashboardInformer(client, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredTektonDashboardInformer constructs a new informer for TektonDashboard 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 NewFilteredTektonDashboardInformer(client versioned.Interface, 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.OperatorV1alpha1().TektonDashboards().List(options)
+			},
+			WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.OperatorV1alpha1().TektonDashboards().Watch(options)
+			},
+		},
+		&operatorv1alpha1.TektonDashboard{},
+		resyncPeriod,
+		indexers,
+	)
+}
+
+func (f *tektonDashboardInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+	return NewFilteredTektonDashboardInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *tektonDashboardInformer) Informer() cache.SharedIndexInformer {
+	return f.factory.InformerFor(&operatorv1alpha1.TektonDashboard{}, f.defaultInformer)
+}
+
+func (f *tektonDashboardInformer) Lister() v1alpha1.TektonDashboardLister {
+	return v1alpha1.NewTektonDashboardLister(f.Informer().GetIndexer())
+}
diff --git a/pkg/client/injection/informers/operator/v1alpha1/tektondashboard/fake/fake.go b/pkg/client/injection/informers/operator/v1alpha1/tektondashboard/fake/fake.go
new file mode 100644
index 000000000..a1a32d6e2
--- /dev/null
+++ b/pkg/client/injection/informers/operator/v1alpha1/tektondashboard/fake/fake.go
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by injection-gen. DO NOT EDIT.
+
+package fake
+
+import (
+	context "context"
+
+	fake "github.com/tektoncd/operator/pkg/client/injection/informers/factory/fake"
+	tektondashboard "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektondashboard"
+	controller "knative.dev/pkg/controller"
+	injection "knative.dev/pkg/injection"
+)
+
+var Get = tektondashboard.Get
+
+func init() {
+	injection.Fake.RegisterInformer(withInformer)
+}
+
+func withInformer(ctx context.Context) (context.Context, controller.Informer) {
+	f := fake.Get(ctx)
+	inf := f.Operator().V1alpha1().TektonDashboards()
+	return context.WithValue(ctx, tektondashboard.Key{}, inf), inf.Informer()
+}
diff --git a/pkg/client/injection/informers/operator/v1alpha1/tektondashboard/tektondashboard.go b/pkg/client/injection/informers/operator/v1alpha1/tektondashboard/tektondashboard.go
new file mode 100644
index 000000000..cb519081e
--- /dev/null
+++ b/pkg/client/injection/informers/operator/v1alpha1/tektondashboard/tektondashboard.go
@@ -0,0 +1,52 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by injection-gen. DO NOT EDIT.
+
+package tektondashboard
+
+import (
+	context "context"
+
+	v1alpha1 "github.com/tektoncd/operator/pkg/client/informers/externalversions/operator/v1alpha1"
+	factory "github.com/tektoncd/operator/pkg/client/injection/informers/factory"
+	controller "knative.dev/pkg/controller"
+	injection "knative.dev/pkg/injection"
+	logging "knative.dev/pkg/logging"
+)
+
+func init() {
+	injection.Default.RegisterInformer(withInformer)
+}
+
+// Key is used for associating the Informer inside the context.Context.
+type Key struct{}
+
+func withInformer(ctx context.Context) (context.Context, controller.Informer) {
+	f := factory.Get(ctx)
+	inf := f.Operator().V1alpha1().TektonDashboards()
+	return context.WithValue(ctx, Key{}, inf), inf.Informer()
+}
+
+// Get extracts the typed informer from the context.
+func Get(ctx context.Context) v1alpha1.TektonDashboardInformer {
+	untyped := ctx.Value(Key{})
+	if untyped == nil {
+		logging.FromContext(ctx).Panic(
+			"Unable to fetch github.com/tektoncd/operator/pkg/client/informers/externalversions/operator/v1alpha1.TektonDashboardInformer from context.")
+	}
+	return untyped.(v1alpha1.TektonDashboardInformer)
+}
diff --git a/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/controller.go b/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/controller.go
new file mode 100644
index 000000000..803910243
--- /dev/null
+++ b/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/controller.go
@@ -0,0 +1,142 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by injection-gen. DO NOT EDIT.
+
+package tektondashboard
+
+import (
+	context "context"
+	fmt "fmt"
+	reflect "reflect"
+	strings "strings"
+
+	versionedscheme "github.com/tektoncd/operator/pkg/client/clientset/versioned/scheme"
+	client "github.com/tektoncd/operator/pkg/client/injection/client"
+	tektondashboard "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektondashboard"
+	corev1 "k8s.io/api/core/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	scheme "k8s.io/client-go/kubernetes/scheme"
+	v1 "k8s.io/client-go/kubernetes/typed/core/v1"
+	record "k8s.io/client-go/tools/record"
+	kubeclient "knative.dev/pkg/client/injection/kube/client"
+	controller "knative.dev/pkg/controller"
+	logging "knative.dev/pkg/logging"
+	reconciler "knative.dev/pkg/reconciler"
+)
+
+const (
+	defaultControllerAgentName = "tektondashboard-controller"
+	defaultFinalizerName       = "tektondashboards.operator.tekton.dev"
+)
+
+// NewImpl returns a controller.Impl that handles queuing and feeding work from
+// the queue through an implementation of controller.Reconciler, delegating to
+// the provided Interface and optional Finalizer methods. OptionsFn is used to return
+// controller.Options to be used but the internal reconciler.
+func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
+	logger := logging.FromContext(ctx)
+
+	// Check the options function input. It should be 0 or 1.
+	if len(optionsFns) > 1 {
+		logger.Fatalf("up to one options function is supported, found %d", len(optionsFns))
+	}
+
+	tektondashboardInformer := tektondashboard.Get(ctx)
+
+	lister := tektondashboardInformer.Lister()
+
+	rec := &reconcilerImpl{
+		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
+			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
+				all, err := lister.List(labels.Everything())
+				if err != nil {
+					return err
+				}
+				for _, elt := range all {
+					// TODO: Consider letting users specify a filter in options.
+					enq(bkt, types.NamespacedName{
+						Namespace: elt.GetNamespace(),
+						Name:      elt.GetName(),
+					})
+				}
+				return nil
+			},
+		},
+		Client:        client.Get(ctx),
+		Lister:        lister,
+		reconciler:    r,
+		finalizerName: defaultFinalizerName,
+	}
+
+	t := reflect.TypeOf(r).Elem()
+	queueName := fmt.Sprintf("%s.%s", strings.ReplaceAll(t.PkgPath(), "/", "-"), t.Name())
+
+	impl := controller.NewImpl(rec, logger, queueName)
+	agentName := defaultControllerAgentName
+
+	// Pass impl to the options. Save any optional results.
+	for _, fn := range optionsFns {
+		opts := fn(impl)
+		if opts.ConfigStore != nil {
+			rec.configStore = opts.ConfigStore
+		}
+		if opts.FinalizerName != "" {
+			rec.finalizerName = opts.FinalizerName
+		}
+		if opts.AgentName != "" {
+			agentName = opts.AgentName
+		}
+		if opts.SkipStatusUpdates {
+			rec.skipStatusUpdates = true
+		}
+	}
+
+	rec.Recorder = createRecorder(ctx, agentName)
+
+	return impl
+}
+
+func createRecorder(ctx context.Context, agentName string) record.EventRecorder {
+	logger := logging.FromContext(ctx)
+
+	recorder := controller.GetEventRecorder(ctx)
+	if recorder == nil {
+		// Create event broadcaster
+		logger.Debug("Creating event broadcaster")
+		eventBroadcaster := record.NewBroadcaster()
+		watches := []watch.Interface{
+			eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof),
+			eventBroadcaster.StartRecordingToSink(
+				&v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}),
+		}
+		recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName})
+		go func() {
+			<-ctx.Done()
+			for _, w := range watches {
+				w.Stop()
+			}
+		}()
+	}
+
+	return recorder
+}
+
+func init() {
+	versionedscheme.AddToScheme(scheme.Scheme)
+}
diff --git a/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/reconciler.go b/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/reconciler.go
new file mode 100644
index 000000000..5d7560c26
--- /dev/null
+++ b/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/reconciler.go
@@ -0,0 +1,441 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by injection-gen. DO NOT EDIT.
+
+package tektondashboard
+
+import (
+	context "context"
+	json "encoding/json"
+	fmt "fmt"
+	reflect "reflect"
+
+	v1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	versioned "github.com/tektoncd/operator/pkg/client/clientset/versioned"
+	operatorv1alpha1 "github.com/tektoncd/operator/pkg/client/listers/operator/v1alpha1"
+	zap "go.uber.org/zap"
+	v1 "k8s.io/api/core/v1"
+	equality "k8s.io/apimachinery/pkg/api/equality"
+	errors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	types "k8s.io/apimachinery/pkg/types"
+	sets "k8s.io/apimachinery/pkg/util/sets"
+	record "k8s.io/client-go/tools/record"
+	controller "knative.dev/pkg/controller"
+	kmp "knative.dev/pkg/kmp"
+	logging "knative.dev/pkg/logging"
+	reconciler "knative.dev/pkg/reconciler"
+)
+
+// Interface defines the strongly typed interfaces to be implemented by a
+// controller reconciling v1alpha1.TektonDashboard.
+type Interface interface {
+	// ReconcileKind implements custom logic to reconcile v1alpha1.TektonDashboard. Any changes
+	// to the objects .Status or .Finalizers will be propagated to the stored
+	// object. It is recommended that implementors do not call any update calls
+	// for the Kind inside of ReconcileKind, it is the responsibility of the calling
+	// controller to propagate those properties. The resource passed to ReconcileKind
+	// will always have an empty deletion timestamp.
+	ReconcileKind(ctx context.Context, o *v1alpha1.TektonDashboard) reconciler.Event
+}
+
+// Finalizer defines the strongly typed interfaces to be implemented by a
+// controller finalizing v1alpha1.TektonDashboard.
+type Finalizer interface {
+	// FinalizeKind implements custom logic to finalize v1alpha1.TektonDashboard. Any changes
+	// to the objects .Status or .Finalizers will be ignored. Returning a nil or
+	// Normal type reconciler.Event will allow the finalizer to be deleted on
+	// the resource. The resource passed to FinalizeKind will always have a set
+	// deletion timestamp.
+	FinalizeKind(ctx context.Context, o *v1alpha1.TektonDashboard) reconciler.Event
+}
+
+// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a
+// controller reconciling v1alpha1.TektonDashboard if they want to process resources for which
+// they are not the leader.
+type ReadOnlyInterface interface {
+	// ObserveKind implements logic to observe v1alpha1.TektonDashboard.
+	// This method should not write to the API.
+	ObserveKind(ctx context.Context, o *v1alpha1.TektonDashboard) reconciler.Event
+}
+
+// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
+// controller finalizing v1alpha1.TektonDashboard if they want to process tombstoned resources
+// even when they are not the leader.  Due to the nature of how finalizers are handled
+// there are no guarantees that this will be called.
+type ReadOnlyFinalizer interface {
+	// ObserveFinalizeKind implements custom logic to observe the final state of v1alpha1.TektonDashboard.
+	// This method should not write to the API.
+	ObserveFinalizeKind(ctx context.Context, o *v1alpha1.TektonDashboard) reconciler.Event
+}
+
+type doReconcile func(ctx context.Context, o *v1alpha1.TektonDashboard) reconciler.Event
+
+// reconcilerImpl implements controller.Reconciler for v1alpha1.TektonDashboard resources.
+type reconcilerImpl struct {
+	// LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware
+	reconciler.LeaderAwareFuncs
+
+	// Client is used to write back status updates.
+	Client versioned.Interface
+
+	// Listers index properties about resources
+	Lister operatorv1alpha1.TektonDashboardLister
+
+	// Recorder is an event recorder for recording Event resources to the
+	// Kubernetes API.
+	Recorder record.EventRecorder
+
+	// configStore allows for decorating a context with config maps.
+	// +optional
+	configStore reconciler.ConfigStore
+
+	// reconciler is the implementation of the business logic of the resource.
+	reconciler Interface
+
+	// finalizerName is the name of the finalizer to reconcile.
+	finalizerName string
+
+	// skipStatusUpdates configures whether or not this reconciler automatically updates
+	// the status of the reconciled resource.
+	skipStatusUpdates bool
+}
+
+// Check that our Reconciler implements controller.Reconciler
+var _ controller.Reconciler = (*reconcilerImpl)(nil)
+
+// Check that our generated Reconciler is always LeaderAware.
+var _ reconciler.LeaderAware = (*reconcilerImpl)(nil)
+
+func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister operatorv1alpha1.TektonDashboardLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler {
+	// Check the options function input. It should be 0 or 1.
+	if len(options) > 1 {
+		logger.Fatalf("up to one options struct is supported, found %d", len(options))
+	}
+
+	// Fail fast when users inadvertently implement the other LeaderAware interface.
+	// For the typed reconcilers, Promote shouldn't take any arguments.
+	if _, ok := r.(reconciler.LeaderAware); ok {
+		logger.Fatalf("%T implements the incorrect LeaderAware interface.  Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r)
+	}
+	// TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer.
+
+	rec := &reconcilerImpl{
+		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
+			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
+				all, err := lister.List(labels.Everything())
+				if err != nil {
+					return err
+				}
+				for _, elt := range all {
+					// TODO: Consider letting users specify a filter in options.
+					enq(bkt, types.NamespacedName{
+						Namespace: elt.GetNamespace(),
+						Name:      elt.GetName(),
+					})
+				}
+				return nil
+			},
+		},
+		Client:        client,
+		Lister:        lister,
+		Recorder:      recorder,
+		reconciler:    r,
+		finalizerName: defaultFinalizerName,
+	}
+
+	for _, opts := range options {
+		if opts.ConfigStore != nil {
+			rec.configStore = opts.ConfigStore
+		}
+		if opts.FinalizerName != "" {
+			rec.finalizerName = opts.FinalizerName
+		}
+		if opts.SkipStatusUpdates {
+			rec.skipStatusUpdates = true
+		}
+	}
+
+	return rec
+}
+
+// Reconcile implements controller.Reconciler
+func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
+	logger := logging.FromContext(ctx)
+
+	// Initialize the reconciler state. This will convert the namespace/name
+	// string into a distinct namespace and name, determin if this instance of
+	// the reconciler is the leader, and any additional interfaces implemented
+	// by the reconciler. Returns an error is the resource key is invalid.
+	s, err := newState(key, r)
+	if err != nil {
+		logger.Errorf("invalid resource key: %s", key)
+		return nil
+	}
+
+	// If we are not the leader, and we don't implement either ReadOnly
+	// observer interfaces, then take a fast-path out.
+	if s.isNotLeaderNorObserver() {
+		return nil
+	}
+
+	// If configStore is set, attach the frozen configuration to the context.
+	if r.configStore != nil {
+		ctx = r.configStore.ToContext(ctx)
+	}
+
+	// Add the recorder to context.
+	ctx = controller.WithEventRecorder(ctx, r.Recorder)
+
+	// Get the resource with this namespace/name.
+
+	getter := r.Lister
+
+	original, err := getter.Get(s.name)
+
+	if errors.IsNotFound(err) {
+		// The resource may no longer exist, in which case we stop processing.
+		logger.Debugf("resource %q no longer exists", key)
+		return nil
+	} else if err != nil {
+		return err
+	}
+
+	// Don't modify the informers copy.
+	resource := original.DeepCopy()
+
+	var reconcileEvent reconciler.Event
+
+	name, do := s.reconcileMethodFor(resource)
+	// Append the target method to the logger.
+	logger = logger.With(zap.String("targetMethod", name))
+	switch name {
+	case reconciler.DoReconcileKind:
+		// Append the target method to the logger.
+		logger = logger.With(zap.String("targetMethod", "ReconcileKind"))
+
+		// Set and update the finalizer on resource if r.reconciler
+		// implements Finalizer.
+		if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil {
+			return fmt.Errorf("failed to set finalizers: %w", err)
+		}
+
+		// Reconcile this copy of the resource and then write back any status
+		// updates regardless of whether the reconciliation errored out.
+		reconcileEvent = do(ctx, resource)
+
+	case reconciler.DoFinalizeKind:
+		// For finalizing reconcilers, if this resource being marked for deletion
+		// and reconciled cleanly (nil or normal event), remove the finalizer.
+		reconcileEvent = do(ctx, resource)
+
+		if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil {
+			return fmt.Errorf("failed to clear finalizers: %w", err)
+		}
+
+	case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
+		// Observe any changes to this resource, since we are not the leader.
+		reconcileEvent = do(ctx, resource)
+
+	}
+
+	// Synchronize the status.
+	switch {
+	case r.skipStatusUpdates:
+		// This reconciler implementation is configured to skip resource updates.
+		// This may mean this reconciler does not observe spec, but reconciles external changes.
+	case equality.Semantic.DeepEqual(original.Status, resource.Status):
+		// If we didn't change anything then don't call updateStatus.
+		// This is important because the copy we loaded from the injectionInformer's
+		// cache may be stale and we don't want to overwrite a prior update
+		// to status with this stale state.
+	case !s.isLeader:
+		// High-availability reconcilers may have many replicas watching the resource, but only
+		// the elected leader is expected to write modifications.
+		logger.Warn("Saw status changes when we aren't the leader!")
+	default:
+		if err = r.updateStatus(ctx, original, resource); err != nil {
+			logger.Warnw("Failed to update resource status", zap.Error(err))
+			r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed",
+				"Failed to update status for %q: %v", resource.Name, err)
+			return err
+		}
+	}
+
+	// Report the reconciler event, if any.
+	if reconcileEvent != nil {
+		var event *reconciler.ReconcilerEvent
+		if reconciler.EventAs(reconcileEvent, &event) {
+			logger.Infow("Returned an event", zap.Any("event", reconcileEvent))
+			r.Recorder.Eventf(resource, event.EventType, event.Reason, event.Format, event.Args...)
+
+			// the event was wrapped inside an error, consider the reconciliation as failed
+			if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent {
+				return reconcileEvent
+			}
+			return nil
+		}
+
+		logger.Errorw("Returned an error", zap.Error(reconcileEvent))
+		r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error())
+		return reconcileEvent
+	}
+
+	return nil
+}
+
+func (r *reconcilerImpl) updateStatus(ctx context.Context, existing *v1alpha1.TektonDashboard, desired *v1alpha1.TektonDashboard) error {
+	existing = existing.DeepCopy()
+	return reconciler.RetryUpdateConflicts(func(attempts int) (err error) {
+		// The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API.
+		if attempts > 0 {
+
+			getter := r.Client.OperatorV1alpha1().TektonDashboards()
+
+			existing, err = getter.Get(desired.Name, metav1.GetOptions{})
+			if err != nil {
+				return err
+			}
+		}
+
+		// If there's nothing to update, just return.
+		if reflect.DeepEqual(existing.Status, desired.Status) {
+			return nil
+		}
+
+		if diff, err := kmp.SafeDiff(existing.Status, desired.Status); err == nil && diff != "" {
+			logging.FromContext(ctx).Debugf("Updating status with: %s", diff)
+		}
+
+		existing.Status = desired.Status
+
+		updater := r.Client.OperatorV1alpha1().TektonDashboards()
+
+		_, err = updater.UpdateStatus(existing)
+		return err
+	})
+}
+
+// updateFinalizersFiltered will update the Finalizers of the resource.
+// TODO: this method could be generic and sync all finalizers. For now it only
+// updates defaultFinalizerName or its override.
+func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1alpha1.TektonDashboard) (*v1alpha1.TektonDashboard, error) {
+
+	getter := r.Lister
+
+	actual, err := getter.Get(resource.Name)
+	if err != nil {
+		return resource, err
+	}
+
+	// Don't modify the informers copy.
+	existing := actual.DeepCopy()
+
+	var finalizers []string
+
+	// If there's nothing to update, just return.
+	existingFinalizers := sets.NewString(existing.Finalizers...)
+	desiredFinalizers := sets.NewString(resource.Finalizers...)
+
+	if desiredFinalizers.Has(r.finalizerName) {
+		if existingFinalizers.Has(r.finalizerName) {
+			// Nothing to do.
+			return resource, nil
+		}
+		// Add the finalizer.
+		finalizers = append(existing.Finalizers, r.finalizerName)
+	} else {
+		if !existingFinalizers.Has(r.finalizerName) {
+			// Nothing to do.
+			return resource, nil
+		}
+		// Remove the finalizer.
+		existingFinalizers.Delete(r.finalizerName)
+		finalizers = existingFinalizers.List()
+	}
+
+	mergePatch := map[string]interface{}{
+		"metadata": map[string]interface{}{
+			"finalizers":      finalizers,
+			"resourceVersion": existing.ResourceVersion,
+		},
+	}
+
+	patch, err := json.Marshal(mergePatch)
+	if err != nil {
+		return resource, err
+	}
+
+	patcher := r.Client.OperatorV1alpha1().TektonDashboards()
+
+	resourceName := resource.Name
+	resource, err = patcher.Patch(resourceName, types.MergePatchType, patch)
+	if err != nil {
+		r.Recorder.Eventf(resource, v1.EventTypeWarning, "FinalizerUpdateFailed",
+			"Failed to update finalizers for %q: %v", resourceName, err)
+	} else {
+		r.Recorder.Eventf(resource, v1.EventTypeNormal, "FinalizerUpdate",
+			"Updated %q finalizers", resource.GetName())
+	}
+	return resource, err
+}
+
+func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1alpha1.TektonDashboard) (*v1alpha1.TektonDashboard, error) {
+	if _, ok := r.reconciler.(Finalizer); !ok {
+		return resource, nil
+	}
+
+	finalizers := sets.NewString(resource.Finalizers...)
+
+	// If this resource is not being deleted, mark the finalizer.
+	if resource.GetDeletionTimestamp().IsZero() {
+		finalizers.Insert(r.finalizerName)
+	}
+
+	resource.Finalizers = finalizers.List()
+
+	// Synchronize the finalizers filtered by r.finalizerName.
+	return r.updateFinalizersFiltered(ctx, resource)
+}
+
+func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1alpha1.TektonDashboard, reconcileEvent reconciler.Event) (*v1alpha1.TektonDashboard, error) {
+	if _, ok := r.reconciler.(Finalizer); !ok {
+		return resource, nil
+	}
+	if resource.GetDeletionTimestamp().IsZero() {
+		return resource, nil
+	}
+
+	finalizers := sets.NewString(resource.Finalizers...)
+
+	if reconcileEvent != nil {
+		var event *reconciler.ReconcilerEvent
+		if reconciler.EventAs(reconcileEvent, &event) {
+			if event.EventType == v1.EventTypeNormal {
+				finalizers.Delete(r.finalizerName)
+			}
+		}
+	} else {
+		finalizers.Delete(r.finalizerName)
+	}
+
+	resource.Finalizers = finalizers.List()
+
+	// Synchronize the finalizers filtered by r.finalizerName.
+	return r.updateFinalizersFiltered(ctx, resource)
+}
diff --git a/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/state.go b/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/state.go
new file mode 100644
index 000000000..34c7e1a4c
--- /dev/null
+++ b/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard/state.go
@@ -0,0 +1,106 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by injection-gen. DO NOT EDIT.
+
+package tektondashboard
+
+import (
+	fmt "fmt"
+
+	v1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	types "k8s.io/apimachinery/pkg/types"
+	cache "k8s.io/client-go/tools/cache"
+	reconciler "knative.dev/pkg/reconciler"
+)
+
+// state is used to track the state of a reconciler in a single run.
+type state struct {
+	// Key is the original reconciliation key from the queue.
+	key string
+	// Namespace is the namespace split from the reconciliation key.
+	namespace string
+	// Namespace is the name split from the reconciliation key.
+	name string
+	// reconciler is the reconciler.
+	reconciler Interface
+	// rof is the read only interface cast of the reconciler.
+	roi ReadOnlyInterface
+	// IsROI (Read Only Interface) the reconciler only observes reconciliation.
+	isROI bool
+	// rof is the read only finalizer cast of the reconciler.
+	rof ReadOnlyFinalizer
+	// IsROF (Read Only Finalizer) the reconciler only observes finalize.
+	isROF bool
+	// IsLeader the instance of the reconciler is the elected leader.
+	isLeader bool
+}
+
+func newState(key string, r *reconcilerImpl) (*state, error) {
+	// Convert the namespace/name string into a distinct namespace and name
+	namespace, name, err := cache.SplitMetaNamespaceKey(key)
+	if err != nil {
+		return nil, fmt.Errorf("invalid resource key: %s", key)
+	}
+
+	roi, isROI := r.reconciler.(ReadOnlyInterface)
+	rof, isROF := r.reconciler.(ReadOnlyFinalizer)
+
+	isLeader := r.IsLeaderFor(types.NamespacedName{
+		Namespace: namespace,
+		Name:      name,
+	})
+
+	return &state{
+		key:        key,
+		namespace:  namespace,
+		name:       name,
+		reconciler: r.reconciler,
+		roi:        roi,
+		isROI:      isROI,
+		rof:        rof,
+		isROF:      isROF,
+		isLeader:   isLeader,
+	}, nil
+}
+
+// isNotLeaderNorObserver checks to see if this reconciler with the current
+// state is enabled to do any work or not.
+// isNotLeaderNorObserver returns true when there is no work possible for the
+// reconciler.
+func (s *state) isNotLeaderNorObserver() bool {
+	if !s.isLeader && !s.isROI && !s.isROF {
+		// If we are not the leader, and we don't implement either ReadOnly
+		// interface, then take a fast-path out.
+		return true
+	}
+	return false
+}
+
+func (s *state) reconcileMethodFor(o *v1alpha1.TektonDashboard) (string, doReconcile) {
+	if o.GetDeletionTimestamp().IsZero() {
+		if s.isLeader {
+			return reconciler.DoReconcileKind, s.reconciler.ReconcileKind
+		} else if s.isROI {
+			return reconciler.DoObserveKind, s.roi.ObserveKind
+		}
+	} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
+		return reconciler.DoFinalizeKind, fin.FinalizeKind
+	} else if !s.isLeader && s.isROF {
+		return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
+	}
+	return "unknown", nil
+}
diff --git a/pkg/client/listers/operator/v1alpha1/expansion_generated.go b/pkg/client/listers/operator/v1alpha1/expansion_generated.go
index 03c475642..84dc274bc 100644
--- a/pkg/client/listers/operator/v1alpha1/expansion_generated.go
+++ b/pkg/client/listers/operator/v1alpha1/expansion_generated.go
@@ -18,6 +18,10 @@ limitations under the License.
 
 package v1alpha1
 
+// TektonDashboardListerExpansion allows custom methods to be added to
+// TektonDashboardLister.
+type TektonDashboardListerExpansion interface{}
+
 // TektonPipelineListerExpansion allows custom methods to be added to
 // TektonPipelineLister.
 type TektonPipelineListerExpansion interface{}
diff --git a/pkg/client/listers/operator/v1alpha1/tektondashboard.go b/pkg/client/listers/operator/v1alpha1/tektondashboard.go
new file mode 100644
index 000000000..11f74fa9d
--- /dev/null
+++ b/pkg/client/listers/operator/v1alpha1/tektondashboard.go
@@ -0,0 +1,65 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	v1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/client-go/tools/cache"
+)
+
+// TektonDashboardLister helps list TektonDashboards.
+type TektonDashboardLister interface {
+	// List lists all TektonDashboards in the indexer.
+	List(selector labels.Selector) (ret []*v1alpha1.TektonDashboard, err error)
+	// Get retrieves the TektonDashboard from the index for a given name.
+	Get(name string) (*v1alpha1.TektonDashboard, error)
+	TektonDashboardListerExpansion
+}
+
+// tektonDashboardLister implements the TektonDashboardLister interface.
+type tektonDashboardLister struct {
+	indexer cache.Indexer
+}
+
+// NewTektonDashboardLister returns a new TektonDashboardLister.
+func NewTektonDashboardLister(indexer cache.Indexer) TektonDashboardLister {
+	return &tektonDashboardLister{indexer: indexer}
+}
+
+// List lists all TektonDashboards in the indexer.
+func (s *tektonDashboardLister) List(selector labels.Selector) (ret []*v1alpha1.TektonDashboard, err error) {
+	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1alpha1.TektonDashboard))
+	})
+	return ret, err
+}
+
+// Get retrieves the TektonDashboard from the index for a given name.
+func (s *tektonDashboardLister) Get(name string) (*v1alpha1.TektonDashboard, error) {
+	obj, exists, err := s.indexer.GetByKey(name)
+	if err != nil {
+		return nil, err
+	}
+	if !exists {
+		return nil, errors.NewNotFound(v1alpha1.Resource("tektondashboard"), name)
+	}
+	return obj.(*v1alpha1.TektonDashboard), nil
+}
diff --git a/pkg/reconciler/common/deployments.go b/pkg/reconciler/common/deployments.go
index c40ca2584..c6e5fa5cc 100644
--- a/pkg/reconciler/common/deployments.go
+++ b/pkg/reconciler/common/deployments.go
@@ -18,7 +18,6 @@ package common
 
 import (
 	"context"
-
 	mf "github.com/manifestival/manifestival"
 	v1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
 	appsv1 "k8s.io/api/apps/v1"
@@ -46,6 +45,7 @@ func CheckDeployments(ctx context.Context, manifest *mf.Manifest, instance v1alp
 		}
 		if !isDeploymentAvailable(deployment) {
 			status.MarkDeploymentsNotReady()
+			//return fmt.Errorf("deployment not yet ready")
 			return nil
 		}
 	}
diff --git a/pkg/reconciler/common/releases.go b/pkg/reconciler/common/releases.go
index 1449a8582..cedcd980e 100644
--- a/pkg/reconciler/common/releases.go
+++ b/pkg/reconciler/common/releases.go
@@ -91,6 +91,8 @@ func componentDir(instance v1alpha1.TektonComponent) string {
 		return filepath.Join(koDataDir, "tekton-pipeline")
 	case *v1alpha1.TektonTrigger:
 		return filepath.Join(koDataDir, "tekton-trigger")
+	case *v1alpha1.TektonDashboard:
+		return filepath.Join(koDataDir, "tekton-dashboard")
 	}
 	return ""
 }
diff --git a/pkg/reconciler/kubernetes/tektondashboard/controller.go b/pkg/reconciler/kubernetes/tektondashboard/controller.go
new file mode 100644
index 000000000..2576b54e6
--- /dev/null
+++ b/pkg/reconciler/kubernetes/tektondashboard/controller.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tektondashboard
+
+import (
+	"context"
+
+	"github.com/go-logr/zapr"
+	mfc "github.com/manifestival/client-go-client"
+	mf "github.com/manifestival/manifestival"
+	"go.uber.org/zap"
+	"k8s.io/client-go/tools/cache"
+
+	"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	operatorclient "github.com/tektoncd/operator/pkg/client/injection/client"
+	tektonDashboardinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektondashboard"
+	tektonPipelineinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonpipeline"
+	tektonDashboardreconciler "github.com/tektoncd/operator/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard"
+	"github.com/tektoncd/operator/pkg/reconciler/common"
+	kubeclient "knative.dev/pkg/client/injection/kube/client"
+	deploymentinformer "knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment"
+	"knative.dev/pkg/configmap"
+	"knative.dev/pkg/controller"
+	"knative.dev/pkg/injection"
+	"knative.dev/pkg/logging"
+)
+
+// NewController initializes the controller and is called by the generated code
+// Registers eventhandlers to enqueue events
+func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
+	return NewExtendedController(common.NoExtension)(ctx, cmw)
+}
+
+// NewExtendedController returns a controller extended to a specific platform
+func NewExtendedController(generator common.ExtensionGenerator) injection.ControllerConstructor {
+	return func(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
+		tektonPipelineInformer := tektonPipelineinformer.Get(ctx)
+		tektonDashboardInformer := tektonDashboardinformer.Get(ctx)
+		deploymentInformer := deploymentinformer.Get(ctx)
+		kubeClient := kubeclient.Get(ctx)
+		logger := logging.FromContext(ctx)
+
+		mfclient, err := mfc.NewClient(injection.GetConfig(ctx))
+		if err != nil {
+			logger.Fatalw("Error creating client from injected config", zap.Error(err))
+		}
+		mflogger := zapr.NewLogger(logger.Named("manifestival").Desugar())
+		manifest, _ := mf.ManifestFrom(mf.Slice{}, mf.UseClient(mfclient), mf.UseLogger(mflogger))
+
+		c := &Reconciler{
+			kubeClientSet:     kubeClient,
+			operatorClientSet: operatorclient.Get(ctx),
+			extension:         generator(ctx),
+			manifest:          manifest,
+			pipelineInformer:  tektonPipelineInformer,
+		}
+		impl := tektonDashboardreconciler.NewImpl(ctx, c)
+
+		logger.Info("Setting up event handlers")
+
+		tektonDashboardInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue))
+
+		deploymentInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
+			FilterFunc: controller.FilterControllerGVK(v1alpha1.SchemeGroupVersion.WithKind("TektonDashboards")),
+			Handler:    controller.HandleAll(impl.EnqueueControllerOf),
+		})
+
+		return impl
+	}
+}
diff --git a/pkg/reconciler/kubernetes/tektondashboard/tektondashboard.go b/pkg/reconciler/kubernetes/tektondashboard/tektondashboard.go
new file mode 100644
index 000000000..541e9835a
--- /dev/null
+++ b/pkg/reconciler/kubernetes/tektondashboard/tektondashboard.go
@@ -0,0 +1,152 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tektondashboard
+
+import (
+	"context"
+	"fmt"
+
+	mf "github.com/manifestival/manifestival"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+
+	"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	clientset "github.com/tektoncd/operator/pkg/client/clientset/versioned"
+	pipelineinformer "github.com/tektoncd/operator/pkg/client/informers/externalversions/operator/v1alpha1"
+	tektondashboardreconciler "github.com/tektoncd/operator/pkg/client/injection/reconciler/operator/v1alpha1/tektondashboard"
+	"github.com/tektoncd/operator/pkg/reconciler/common"
+	"knative.dev/pkg/logging"
+	pkgreconciler "knative.dev/pkg/reconciler"
+)
+
+// Reconciler implements controller.Reconciler for Knativeserving resources.
+type Reconciler struct {
+	// kubeClientSet allows us to talk to the k8s for core APIs
+	kubeClientSet kubernetes.Interface
+	// operatorClientSet allows us to configure operator objects
+	operatorClientSet clientset.Interface
+	// manifest is empty, but with a valid client and logger. all
+	// manifests are immutable, and any created during reconcile are
+	// expected to be appended to this one, obviating the passing of
+	// client & logger
+	manifest mf.Manifest
+	// Platform-specific behavior to affect the transform
+	extension common.Extension
+
+	pipelineInformer pipelineinformer.TektonPipelineInformer
+}
+
+// Check that our Reconciler implements controller.Reconciler
+var _ tektondashboardreconciler.Interface = (*Reconciler)(nil)
+var _ tektondashboardreconciler.Finalizer = (*Reconciler)(nil)
+
+var watchedResourceName = "dashboard"
+
+// FinalizeKind removes all resources after deletion of a TektonDashboards.
+func (r *Reconciler) FinalizeKind(ctx context.Context, original *v1alpha1.TektonDashboard) pkgreconciler.Event {
+	logger := logging.FromContext(ctx)
+
+	// List all TektonDashboards to determine if cluster-scoped resources should be deleted.
+	tps, err := r.operatorClientSet.OperatorV1alpha1().TektonDashboards().List(metav1.ListOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to list all TektonDashboards: %w", err)
+	}
+
+	for _, tp := range tps.Items {
+		if tp.GetDeletionTimestamp().IsZero() {
+			// Not deleting all TektonDashboards. Nothing to do here.
+			return nil
+		}
+	}
+
+	if err := r.extension.Finalize(ctx, original); err != nil {
+		logger.Error("Failed to finalize platform resources", err)
+	}
+	logger.Info("Deleting cluster-scoped resources")
+	manifest, err := r.installed(ctx, original)
+	if err != nil {
+		logger.Error("Unable to fetch installed manifest; no cluster-scoped resources will be finalized", err)
+		return nil
+	}
+	if err := common.Uninstall(manifest); err != nil {
+		logger.Error("Failed to finalize platform resources", err)
+	}
+	return nil
+}
+
+// ReconcileKind compares the actual state with the desired, and attempts to
+// converge the two.
+func (r *Reconciler) ReconcileKind(ctx context.Context, tt *v1alpha1.TektonDashboard) pkgreconciler.Event {
+	logger := logging.FromContext(ctx)
+	tt.Status.InitializeConditions()
+	tt.Status.ObservedGeneration = tt.Generation
+
+	logger.Infow("Reconciling TektonDashboards", "status", tt.Status)
+
+	if tt.GetName() != watchedResourceName {
+		msg := fmt.Sprintf("Resource ignored, Expected Name: %s, Got Name: %s",
+			watchedResourceName,
+			tt.GetName(),
+		)
+		logger.Error(msg)
+		tt.GetStatus().MarkInstallFailed(msg)
+		return nil
+	}
+
+	// find the valid tekton-pipeline installation
+	if _, err := common.PipelineReady(r.pipelineInformer); err != nil {
+		if err.Error() == common.PipelineNotReady {
+			tt.Status.MarkDependencyInstalling("tekton-pipelines is still installing")
+			// wait for pipeline status to change
+			return fmt.Errorf(common.PipelineNotReady)
+		}
+		// (tektonpipeline.opeator.tekton.dev instance not available yet)
+		tt.Status.MarkDependencyMissing("tekton-pipelines does not exist")
+		return err
+	}
+	tt.Status.MarkDependenciesInstalled()
+
+	if err := r.extension.Reconcile(ctx, tt); err != nil {
+		return err
+	}
+	stages := common.Stages{
+		common.AppendTarget,
+		r.transform,
+		common.Install,
+		common.CheckDeployments,
+	}
+	manifest := r.manifest.Append()
+	return stages.Execute(ctx, &manifest, tt)
+}
+
+// transform mutates the passed manifest to one with common, component
+// and platform transformations applied
+func (r *Reconciler) transform(ctx context.Context, manifest *mf.Manifest, comp v1alpha1.TektonComponent) error {
+	instance := comp.(*v1alpha1.TektonDashboard)
+	extra := []mf.Transformer{}
+	extra = append(extra, r.extension.Transformers(instance)...)
+	return common.Transform(ctx, manifest, instance, extra...)
+}
+
+func (r *Reconciler) installed(ctx context.Context, instance v1alpha1.TektonComponent) (*mf.Manifest, error) {
+	// Create new, empty manifest with valid client and logger
+	installed := r.manifest.Append()
+	// TODO: add ingress, etc
+	stages := common.Stages{common.AppendInstalled, r.transform}
+	err := stages.Execute(ctx, &installed, instance)
+	return &installed, err
+}
diff --git a/vendor/github.com/tektoncd/plumbing/OWNERS b/vendor/github.com/tektoncd/plumbing/OWNERS
deleted file mode 100644
index 58ccb789a..000000000
--- a/vendor/github.com/tektoncd/plumbing/OWNERS
+++ /dev/null
@@ -1,11 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- abayer
-- afrittoli
-- bobcatfish
-- dibyom
-- dlorenc
-- kimsterv
-- sbwsg
-- vdemeester
diff --git a/vendor/k8s.io/apimachinery/pkg/api/errors/OWNERS b/vendor/k8s.io/apimachinery/pkg/api/errors/OWNERS
deleted file mode 100644
index 435297a8d..000000000
--- a/vendor/k8s.io/apimachinery/pkg/api/errors/OWNERS
+++ /dev/null
@@ -1,25 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- thockin
-- lavalamp
-- smarterclayton
-- wojtek-t
-- deads2k
-- brendandburns
-- derekwaynecarr
-- caesarxuchao
-- mikedanese
-- liggitt
-- nikhiljindal
-- gmarek
-- erictune
-- saad-ali
-- janetkuo
-- tallclair
-- eparis
-- dims
-- hongchaodeng
-- krousey
-- cjcullen
-- david-mcmahon
diff --git a/vendor/k8s.io/apimachinery/pkg/api/meta/OWNERS b/vendor/k8s.io/apimachinery/pkg/api/meta/OWNERS
deleted file mode 100644
index 96bccff1b..000000000
--- a/vendor/k8s.io/apimachinery/pkg/api/meta/OWNERS
+++ /dev/null
@@ -1,23 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- thockin
-- smarterclayton
-- wojtek-t
-- deads2k
-- brendandburns
-- derekwaynecarr
-- caesarxuchao
-- mikedanese
-- liggitt
-- nikhiljindal
-- gmarek
-- janetkuo
-- ncdc
-- eparis
-- dims
-- krousey
-- resouer
-- david-mcmahon
-- mfojtik
-- jianhuiz
diff --git a/vendor/k8s.io/apimachinery/pkg/api/resource/OWNERS b/vendor/k8s.io/apimachinery/pkg/api/resource/OWNERS
deleted file mode 100644
index dc7740190..000000000
--- a/vendor/k8s.io/apimachinery/pkg/api/resource/OWNERS
+++ /dev/null
@@ -1,16 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- thockin
-- lavalamp
-- smarterclayton
-- wojtek-t
-- derekwaynecarr
-- mikedanese
-- saad-ali
-- janetkuo
-- tallclair
-- eparis
-- xiang90
-- mbohlool
-- david-mcmahon
diff --git a/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/OWNERS b/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/OWNERS
deleted file mode 100644
index 77cfb0c1a..000000000
--- a/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/OWNERS
+++ /dev/null
@@ -1,32 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- thockin
-- smarterclayton
-- wojtek-t
-- deads2k
-- brendandburns
-- caesarxuchao
-- liggitt
-- nikhiljindal
-- gmarek
-- erictune
-- davidopp
-- sttts
-- quinton-hoole
-- luxas
-- janetkuo
-- justinsb
-- ncdc
-- soltysh
-- dims
-- madhusudancs
-- hongchaodeng
-- krousey
-- mml
-- mbohlool
-- david-mcmahon
-- therc
-- mqliang
-- kevin-wangzefeng
-- jianhuiz
diff --git a/vendor/k8s.io/apimachinery/pkg/util/mergepatch/OWNERS b/vendor/k8s.io/apimachinery/pkg/util/mergepatch/OWNERS
deleted file mode 100644
index 3f72c69ba..000000000
--- a/vendor/k8s.io/apimachinery/pkg/util/mergepatch/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- pwittrock
-reviewers:
-- mengqiy
-- apelisse
diff --git a/vendor/k8s.io/apimachinery/pkg/util/strategicpatch/OWNERS b/vendor/k8s.io/apimachinery/pkg/util/strategicpatch/OWNERS
deleted file mode 100644
index cfee199fa..000000000
--- a/vendor/k8s.io/apimachinery/pkg/util/strategicpatch/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- pwittrock
-- mengqiy
-reviewers:
-- mengqiy
-- apelisse
diff --git a/vendor/k8s.io/apimachinery/third_party/forked/golang/json/OWNERS b/vendor/k8s.io/apimachinery/third_party/forked/golang/json/OWNERS
deleted file mode 100644
index 3f72c69ba..000000000
--- a/vendor/k8s.io/apimachinery/third_party/forked/golang/json/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- pwittrock
-reviewers:
-- mengqiy
-- apelisse
diff --git a/vendor/k8s.io/client-go/pkg/apis/clientauthentication/OWNERS b/vendor/k8s.io/client-go/pkg/apis/clientauthentication/OWNERS
deleted file mode 100644
index e0ec62deb..000000000
--- a/vendor/k8s.io/client-go/pkg/apis/clientauthentication/OWNERS
+++ /dev/null
@@ -1,9 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-# approval on api packages bubbles to api-approvers
-reviewers:
-- sig-auth-authenticators-approvers
-- sig-auth-authenticators-reviewers
-labels:
-- sig/auth
-
diff --git a/vendor/k8s.io/client-go/rest/OWNERS b/vendor/k8s.io/client-go/rest/OWNERS
deleted file mode 100644
index c02ec6a25..000000000
--- a/vendor/k8s.io/client-go/rest/OWNERS
+++ /dev/null
@@ -1,25 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- thockin
-- smarterclayton
-- caesarxuchao
-- wojtek-t
-- deads2k
-- brendandburns
-- liggitt
-- nikhiljindal
-- gmarek
-- erictune
-- sttts
-- luxas
-- dims
-- errordeveloper
-- hongchaodeng
-- krousey
-- resouer
-- cjcullen
-- rmmh
-- asalkeld
-- juanvallejo
-- lojies
diff --git a/vendor/k8s.io/client-go/tools/auth/OWNERS b/vendor/k8s.io/client-go/tools/auth/OWNERS
deleted file mode 100644
index 3e05d309b..000000000
--- a/vendor/k8s.io/client-go/tools/auth/OWNERS
+++ /dev/null
@@ -1,9 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- sig-auth-authenticators-approvers
-reviewers:
-- sig-auth-authenticators-reviewers
-labels:
-- sig/auth
-
diff --git a/vendor/k8s.io/client-go/tools/cache/OWNERS b/vendor/k8s.io/client-go/tools/cache/OWNERS
deleted file mode 100644
index bd61bc766..000000000
--- a/vendor/k8s.io/client-go/tools/cache/OWNERS
+++ /dev/null
@@ -1,45 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- thockin
-- lavalamp
-- smarterclayton
-- wojtek-t
-- deads2k
-- caesarxuchao
-- liggitt
-- ncdc
-reviewers:
-- thockin
-- lavalamp
-- smarterclayton
-- wojtek-t
-- deads2k
-- brendandburns
-- derekwaynecarr
-- caesarxuchao
-- mikedanese
-- liggitt
-- nikhiljindal
-- erictune
-- davidopp
-- pmorie
-- janetkuo
-- justinsb
-- eparis
-- soltysh
-- jsafrane
-- dims
-- madhusudancs
-- hongchaodeng
-- krousey
-- xiang90
-- mml
-- ingvagabund
-- resouer
-- jessfraz
-- david-mcmahon
-- mfojtik
-- mqliang
-- sdminonne
-- ncdc
diff --git a/vendor/k8s.io/client-go/tools/leaderelection/OWNERS b/vendor/k8s.io/client-go/tools/leaderelection/OWNERS
deleted file mode 100644
index 44d93f84f..000000000
--- a/vendor/k8s.io/client-go/tools/leaderelection/OWNERS
+++ /dev/null
@@ -1,14 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- mikedanese
-- timothysc
-reviewers:
-- wojtek-t
-- deads2k
-- mikedanese
-- gmarek
-- eparis
-- timothysc
-- ingvagabund
-- resouer
diff --git a/vendor/k8s.io/client-go/tools/metrics/OWNERS b/vendor/k8s.io/client-go/tools/metrics/OWNERS
deleted file mode 100644
index ad52d0c5c..000000000
--- a/vendor/k8s.io/client-go/tools/metrics/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- wojtek-t
-- eparis
-- krousey
-- jayunit100
diff --git a/vendor/k8s.io/client-go/tools/record/OWNERS b/vendor/k8s.io/client-go/tools/record/OWNERS
deleted file mode 100644
index 6ce73bb5c..000000000
--- a/vendor/k8s.io/client-go/tools/record/OWNERS
+++ /dev/null
@@ -1,29 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- lavalamp
-- smarterclayton
-- wojtek-t
-- deads2k
-- derekwaynecarr
-- caesarxuchao
-- vishh
-- mikedanese
-- liggitt
-- nikhiljindal
-- erictune
-- pmorie
-- dchen1107
-- saad-ali
-- luxas
-- yifan-gu
-- eparis
-- mwielgus
-- timothysc
-- jsafrane
-- dims
-- krousey
-- a-robinson
-- aveshagarwal
-- resouer
-- cjcullen
diff --git a/vendor/k8s.io/client-go/transport/OWNERS b/vendor/k8s.io/client-go/transport/OWNERS
deleted file mode 100644
index a52176903..000000000
--- a/vendor/k8s.io/client-go/transport/OWNERS
+++ /dev/null
@@ -1,9 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- smarterclayton
-- wojtek-t
-- deads2k
-- liggitt
-- krousey
-- caesarxuchao
diff --git a/vendor/k8s.io/client-go/util/cert/OWNERS b/vendor/k8s.io/client-go/util/cert/OWNERS
deleted file mode 100644
index 3cf036438..000000000
--- a/vendor/k8s.io/client-go/util/cert/OWNERS
+++ /dev/null
@@ -1,9 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- sig-auth-certificates-approvers
-reviewers:
-- sig-auth-certificates-reviewers
-labels:
-- sig/auth
-
diff --git a/vendor/k8s.io/client-go/util/keyutil/OWNERS b/vendor/k8s.io/client-go/util/keyutil/OWNERS
deleted file mode 100644
index 470b7a1c9..000000000
--- a/vendor/k8s.io/client-go/util/keyutil/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-approvers:
-- sig-auth-certificates-approvers
-reviewers:
-- sig-auth-certificates-reviewers
-labels:
-- sig/auth
-
diff --git a/vendor/k8s.io/client-go/util/retry/OWNERS b/vendor/k8s.io/client-go/util/retry/OWNERS
deleted file mode 100644
index dec3e88d6..000000000
--- a/vendor/k8s.io/client-go/util/retry/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
-- caesarxuchao
diff --git a/vendor/k8s.io/code-generator/OWNERS b/vendor/k8s.io/code-generator/OWNERS
deleted file mode 100644
index 6f7abe3ed..000000000
--- a/vendor/k8s.io/code-generator/OWNERS
+++ /dev/null
@@ -1,13 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- lavalamp
-- wojtek-t
-- sttts
-reviewers:
-- lavalamp
-- wojtek-t
-- sttts
-labels:
-- sig/api-machinery
-- area/code-generation
diff --git a/vendor/k8s.io/code-generator/cmd/client-gen/OWNERS b/vendor/k8s.io/code-generator/cmd/client-gen/OWNERS
deleted file mode 100644
index 62866d0b1..000000000
--- a/vendor/k8s.io/code-generator/cmd/client-gen/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- lavalamp
-- wojtek-t
-- caesarxuchao
-reviewers:
-- lavalamp
-- wojtek-t
-- caesarxuchao
diff --git a/vendor/k8s.io/code-generator/cmd/go-to-protobuf/OWNERS b/vendor/k8s.io/code-generator/cmd/go-to-protobuf/OWNERS
deleted file mode 100644
index 613659162..000000000
--- a/vendor/k8s.io/code-generator/cmd/go-to-protobuf/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- smarterclayton
-reviewers:
-- smarterclayton
diff --git a/vendor/k8s.io/klog/OWNERS b/vendor/k8s.io/klog/OWNERS
deleted file mode 100644
index 380e514f2..000000000
--- a/vendor/k8s.io/klog/OWNERS
+++ /dev/null
@@ -1,19 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-reviewers:
-  - jayunit100
-  - hoegaarden
-  - andyxning
-  - neolit123
-  - pohly
-  - yagonobre
-  - vincepri
-  - detiber
-approvers:
-  - dims
-  - thockin
-  - justinsb
-  - tallclair
-  - piosz
-  - brancz
-  - DirectXMan12
-  - lavalamp
diff --git a/vendor/k8s.io/klog/v2/OWNERS b/vendor/k8s.io/klog/v2/OWNERS
deleted file mode 100644
index 380e514f2..000000000
--- a/vendor/k8s.io/klog/v2/OWNERS
+++ /dev/null
@@ -1,19 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-reviewers:
-  - jayunit100
-  - hoegaarden
-  - andyxning
-  - neolit123
-  - pohly
-  - yagonobre
-  - vincepri
-  - detiber
-approvers:
-  - dims
-  - thockin
-  - justinsb
-  - tallclair
-  - piosz
-  - brancz
-  - DirectXMan12
-  - lavalamp
diff --git a/vendor/k8s.io/kube-openapi/pkg/generators/rules/OWNERS b/vendor/k8s.io/kube-openapi/pkg/generators/rules/OWNERS
deleted file mode 100644
index 235bc545b..000000000
--- a/vendor/k8s.io/kube-openapi/pkg/generators/rules/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-reviewers:
-- roycaihw
-approvers:
-- roycaihw
diff --git a/vendor/k8s.io/kube-openapi/pkg/util/proto/OWNERS b/vendor/k8s.io/kube-openapi/pkg/util/proto/OWNERS
deleted file mode 100644
index 9621a6a3a..000000000
--- a/vendor/k8s.io/kube-openapi/pkg/util/proto/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-approvers:
-- apelisse
diff --git a/vendor/k8s.io/utils/pointer/OWNERS b/vendor/k8s.io/utils/pointer/OWNERS
deleted file mode 100644
index 0d6392752..000000000
--- a/vendor/k8s.io/utils/pointer/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- apelisse
-- stewart-yu
-- thockin
-reviewers:
-- apelisse
-- stewart-yu
-- thockin
diff --git a/vendor/knative.dev/pkg/apis/OWNERS b/vendor/knative.dev/pkg/apis/OWNERS
deleted file mode 100644
index 1227b2b9e..000000000
--- a/vendor/knative.dev/pkg/apis/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- apis-approvers
-
-reviewers:
-- apis-reviewers
diff --git a/vendor/knative.dev/pkg/configmap/OWNERS b/vendor/knative.dev/pkg/configmap/OWNERS
deleted file mode 100644
index 2480fc6d4..000000000
--- a/vendor/knative.dev/pkg/configmap/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- configmap-approvers
diff --git a/vendor/knative.dev/pkg/controller/OWNERS b/vendor/knative.dev/pkg/controller/OWNERS
deleted file mode 100644
index 0b270d53a..000000000
--- a/vendor/knative.dev/pkg/controller/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- controller-approvers
-
-reviewers:
-- controller-reviewers
diff --git a/vendor/knative.dev/pkg/hack/OWNERS b/vendor/knative.dev/pkg/hack/OWNERS
deleted file mode 100644
index c50adc849..000000000
--- a/vendor/knative.dev/pkg/hack/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- productivity-approvers
-
-reviewers:
-- productivity-reviewers
-
-labels:
-- area/test-and-release
diff --git a/vendor/knative.dev/pkg/hash/OWNERS b/vendor/knative.dev/pkg/hash/OWNERS
deleted file mode 100644
index 0b270d53a..000000000
--- a/vendor/knative.dev/pkg/hash/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- controller-approvers
-
-reviewers:
-- controller-reviewers
diff --git a/vendor/knative.dev/pkg/injection/OWNERS b/vendor/knative.dev/pkg/injection/OWNERS
deleted file mode 100644
index dda47512a..000000000
--- a/vendor/knative.dev/pkg/injection/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- mattmoor
-- n3wscott
diff --git a/vendor/knative.dev/pkg/kmeta/OWNERS b/vendor/knative.dev/pkg/kmeta/OWNERS
deleted file mode 100644
index 29b0d9f25..000000000
--- a/vendor/knative.dev/pkg/kmeta/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- kmeta-approvers
diff --git a/vendor/knative.dev/pkg/logging/OWNERS b/vendor/knative.dev/pkg/logging/OWNERS
deleted file mode 100644
index fa4854ba0..000000000
--- a/vendor/knative.dev/pkg/logging/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- logging-approvers
diff --git a/vendor/knative.dev/pkg/metrics/OWNERS b/vendor/knative.dev/pkg/metrics/OWNERS
deleted file mode 100644
index 6d3966df4..000000000
--- a/vendor/knative.dev/pkg/metrics/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- metrics-approvers
diff --git a/vendor/knative.dev/pkg/network/OWNERS b/vendor/knative.dev/pkg/network/OWNERS
deleted file mode 100644
index b14ac5308..000000000
--- a/vendor/knative.dev/pkg/network/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- tcnghia
-- vagababov
diff --git a/vendor/knative.dev/pkg/reconciler/OWNERS b/vendor/knative.dev/pkg/reconciler/OWNERS
deleted file mode 100644
index 0b270d53a..000000000
--- a/vendor/knative.dev/pkg/reconciler/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- controller-approvers
-
-reviewers:
-- controller-reviewers
diff --git a/vendor/knative.dev/pkg/tracing/config/OWNERS b/vendor/knative.dev/pkg/tracing/config/OWNERS
deleted file mode 100644
index 2480fc6d4..000000000
--- a/vendor/knative.dev/pkg/tracing/config/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- configmap-approvers
diff --git a/vendor/knative.dev/pkg/webhook/OWNERS b/vendor/knative.dev/pkg/webhook/OWNERS
deleted file mode 100644
index 001df221f..000000000
--- a/vendor/knative.dev/pkg/webhook/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- webhook-approvers
-
-reviewers:
-- webhook-reviewers
diff --git a/vendor/sigs.k8s.io/yaml/OWNERS b/vendor/sigs.k8s.io/yaml/OWNERS
deleted file mode 100644
index 325b40b07..000000000
--- a/vendor/sigs.k8s.io/yaml/OWNERS
+++ /dev/null
@@ -1,27 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-- dims
-- lavalamp
-- smarterclayton
-- deads2k
-- sttts
-- liggitt
-- caesarxuchao
-reviewers:
-- dims
-- thockin
-- lavalamp
-- smarterclayton
-- wojtek-t
-- deads2k
-- derekwaynecarr
-- caesarxuchao
-- mikedanese
-- liggitt
-- gmarek
-- sttts
-- ncdc
-- tallclair
-labels:
-- sig/api-machinery


Test patch:
diff --git a/pkg/apis/operator/v1alpha1/tektondashboard_lifecycle_test.go b/pkg/apis/operator/v1alpha1/tektondashboard_lifecycle_test.go
new file mode 100644
index 000000000..394a6da28
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/tektondashboard_lifecycle_test.go
@@ -0,0 +1,138 @@
+/*
+Copyright 2020 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	"testing"
+
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	apistest "knative.dev/pkg/apis/testing"
+)
+
+func TestTektonDashboardGroupVersionKind(t *testing.T) {
+	r := &TektonDashboard{}
+	want := schema.GroupVersionKind{
+		Group:   GroupName,
+		Version: SchemaVersion,
+		Kind:    KindTektonDashboard,
+	}
+	if got := r.GroupVersionKind(); got != want {
+		t.Errorf("got: %v, want: %v", got, want)
+	}
+}
+
+func TestTektonDashboardHappyPath(t *testing.T) {
+	tt := &TektonDashboardStatus{}
+	tt.InitializeConditions()
+
+	apistest.CheckConditionOngoing(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionOngoing(tt, InstallSucceeded, t)
+
+	// Install succeeds.
+	tt.MarkInstallSucceeded()
+	// Dependencies are assumed successful too.
+	apistest.CheckConditionSucceeded(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+
+	// Deployments are not available at first.
+	tt.MarkDeploymentsNotReady()
+	apistest.CheckConditionSucceeded(tt, DependenciesInstalled, t)
+	apistest.CheckConditionFailed(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+	if ready := tt.IsReady(); ready {
+		t.Errorf("tt.IsReady() = %v, want false", ready)
+	}
+
+	// Deployments become ready and we're good.
+	tt.MarkDeploymentsAvailable()
+	apistest.CheckConditionSucceeded(tt, DependenciesInstalled, t)
+	apistest.CheckConditionSucceeded(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+	if ready := tt.IsReady(); !ready {
+		t.Errorf("tt.IsReady() = %v, want true", ready)
+	}
+}
+
+func TestTektonDashboardErrorPath(t *testing.T) {
+	tt := &TektonDashboardStatus{}
+	tt.InitializeConditions()
+
+	apistest.CheckConditionOngoing(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionOngoing(tt, InstallSucceeded, t)
+
+	// Install fails.
+	tt.MarkInstallFailed("test")
+	apistest.CheckConditionOngoing(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionFailed(tt, InstallSucceeded, t)
+
+	// Dependencies are installing.
+	tt.MarkDependencyInstalling("testing")
+	apistest.CheckConditionFailed(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionFailed(tt, InstallSucceeded, t)
+
+	// Install now succeeds.
+	tt.MarkInstallSucceeded()
+	apistest.CheckConditionFailed(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+	if ready := tt.IsReady(); ready {
+		t.Errorf("tt.IsReady() = %v, want false", ready)
+	}
+
+	// Deployments become ready
+	tt.MarkDeploymentsAvailable()
+	apistest.CheckConditionFailed(tt, DependenciesInstalled, t)
+	apistest.CheckConditionSucceeded(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+	if ready := tt.IsReady(); ready {
+		t.Errorf("tt.IsReady() = %v, want false", ready)
+	}
+
+	// Finally, dependencies become available.
+	tt.MarkDependenciesInstalled()
+	apistest.CheckConditionSucceeded(tt, DependenciesInstalled, t)
+	apistest.CheckConditionSucceeded(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+	if ready := tt.IsReady(); !ready {
+		t.Errorf("tt.IsReady() = %v, want true", ready)
+	}
+}
+
+func TestTektonDashboardExternalDependency(t *testing.T) {
+	tt := &TektonDashboardStatus{}
+	tt.InitializeConditions()
+
+	// External marks dependency as failed.
+	tt.MarkDependencyMissing("test")
+
+	// Install succeeds.
+	tt.MarkInstallSucceeded()
+	apistest.CheckConditionFailed(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+
+	// Dependencies are now ready.
+	tt.MarkDependenciesInstalled()
+	apistest.CheckConditionSucceeded(tt, DependenciesInstalled, t)
+	apistest.CheckConditionOngoing(tt, DeploymentsAvailable, t)
+	apistest.CheckConditionSucceeded(tt, InstallSucceeded, t)
+}
diff --git a/vendor/knative.dev/pkg/test/OWNERS b/vendor/knative.dev/pkg/test/OWNERS
deleted file mode 100644
index c50adc849..000000000
--- a/vendor/knative.dev/pkg/test/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# The OWNERS file is used by prow to automatically merge approved PRs.
-
-approvers:
-- productivity-approvers
-
-reviewers:
-- productivity-reviewers
-
-labels:
-- area/test-and-release