Repository: tektoncd/operator
Pull request #1454 (2023-04-21 06:45:43)

Configures the installation of TektonChain through TektonConfig
- Initially chains was installed manually and was not part of TektonConfig

- With this patch chains is installed by default when an operator is installed and the profile is basic or all

Signed-off-by: Puneet Punamiya ppunamiy@redhat.com

# Changes

<!-- 🎉🎉🎉 Thank you for the PR!!! 🎉🎉🎉 -->

<!-- Describe your changes here- ideally you can get that description straight from
your descriptive commit message(s)! -->

# Submitter Checklist

These are the criteria that every PR should meet, please check them off as you
review them:

- [ ] Run `make test lint` before submitting a PR
- [ ] Includes [tests](https://github.com/tektoncd/community/blob/master/standards.md#principles) (if functionality changed/added)
- [ ] Includes [docs](https://github.com/tektoncd/community/blob/master/standards.md#principles) (if user facing)
- [ ] Commit messages follow [commit message best practices](https://github.com/tektoncd/community/blob/master/standards.md#commit-messages)

_See [the contribution guide](https://github.com/tektoncd/operator/blob/master/CONTRIBUTING.md) for more details._

# Release Notes
* With this patch chains is installed by default when an operator is installed and the profile is basic or all
<!--
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 **piyush-garg** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @piyush-garg` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://prow.tekton.dev/command-help?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":["piyush-garg"]} -->
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/shared/tektonconfig/chain/chain.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-go-coverage/1649303389126791168/artifacts/line-cov.html#file2) | Do not exist | 72.2% | 

tekton-robot: @PuneetPunamiya: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-tekton-operator-integration-tests | 46f0bf3b9e8ecc05d6a46c805c053bf5abd41ec4 | [link](https://prow.tekton.dev/view/gs/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-integration-tests/1649303388753498112) | true | `/test pull-tekton-operator-integration-tests`

[Full PR test history](https://prow.tekton.dev/pr-history?org=tektoncd&repo=operator&pr=1454). [Your PR dashboard](https://prow.tekton.dev/pr?query=is%3Apr%20state%3Aopen%20author%3APuneetPunamiya).

<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. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
PuneetPunamiya: /test pull-tekton-operator-integration-tests
tekton-robot: @PuneetPunamiya: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-tekton-operator-integration-tests | 46f0bf3b9e8ecc05d6a46c805c053bf5abd41ec4 | [link](https://prow.tekton.dev/view/gs/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-integration-tests/1649343289414914048) | true | `/test pull-tekton-operator-integration-tests`

[Full PR test history](https://prow.tekton.dev/pr-history?org=tektoncd&repo=operator&pr=1454). [Your PR dashboard](https://prow.tekton.dev/pr?query=is%3Apr%20state%3Aopen%20author%3APuneetPunamiya).

<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. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
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/shared/tektonconfig/chain/chain.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-go-coverage/1650475221129367552/artifacts/line-cov.html#file2) | Do not exist | 72.2% | 

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/shared/tektonconfig/chain/chain.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-go-coverage/1650788757437157376/artifacts/line-cov.html#file2) | Do not exist | 72.2% | 

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/shared/tektonconfig/chain/chain.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-go-coverage/1651621540325756928/artifacts/line-cov.html#file2) | Do not exist | 72.2% | 

tekton-robot: @PuneetPunamiya: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-tekton-operator-integration-tests | 70a70e6e02c0b3bb45b2e7d23e4a65d5d845f731 | [link](https://prow.tekton.dev/view/gs/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-integration-tests/1651621540250259456) | true | `/test pull-tekton-operator-integration-tests`

[Full PR test history](https://prow.tekton.dev/pr-history?org=tektoncd&repo=operator&pr=1454). [Your PR dashboard](https://prow.tekton.dev/pr?query=is%3Apr%20state%3Aopen%20author%3APuneetPunamiya).

<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. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
PuneetPunamiya: /test pull-tekton-operator-integration-tests
tekton-robot: @PuneetPunamiya: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-tekton-operator-integration-tests | 70a70e6e02c0b3bb45b2e7d23e4a65d5d845f731 | [link](https://prow.tekton.dev/view/gs/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-integration-tests/1651765863805620224) | true | `/test pull-tekton-operator-integration-tests`

[Full PR test history](https://prow.tekton.dev/pr-history?org=tektoncd&repo=operator&pr=1454). [Your PR dashboard](https://prow.tekton.dev/pr?query=is%3Apr%20state%3Aopen%20author%3APuneetPunamiya).

<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. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
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/shared/tektonconfig/chain/chain.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-go-coverage/1651823492930211840/artifacts/line-cov.html#file2) | Do not exist | 72.2% | 

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/shared/tektonconfig/chain/chain.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-go-coverage/1651858529998344192/artifacts/line-cov.html#file2) | Do not exist | 72.2% | 

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

This pull-request has been approved by: *<a href="https://github.com/tektoncd/operator/pull/1454#pullrequestreview-1399539387" title="Approved">jkandasa</a>*, *<a href="https://github.com/tektoncd/operator/pull/1454#pullrequestreview-1408859923" title="Approved">vdemeester</a>*

The full list of commands accepted by this bot can be found [here](https://prow.tekton.dev/command-help?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: 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/shared/tektonconfig/chain/chain.go](https://storage.cloud.google.com/tekton-prow/pr-logs/pull/tektoncd_operator/1454/pull-tekton-operator-go-coverage/1653352064383717376/artifacts/line-cov.html#file2) | Do not exist | 71.7% | 

piyush-garg: /lgtm

Patch:
diff --git a/pkg/apis/operator/v1alpha1/tektonconfig_types.go b/pkg/apis/operator/v1alpha1/tektonconfig_types.go
index 66c77a9b6..391605090 100644
--- a/pkg/apis/operator/v1alpha1/tektonconfig_types.go
+++ b/pkg/apis/operator/v1alpha1/tektonconfig_types.go
@@ -92,6 +92,9 @@ type TektonConfigSpec struct {
 	// Trigger holds the customizable option for triggers component
 	// +optional
 	Trigger Trigger `json:"trigger,omitempty"`
+	// Chain holds the customizable option for chains component
+	// +optional
+	Chain Chain `json:"chain,omitempty"`
 	// Dashboard holds the customizable options for dashboards component
 	// +optional
 	Dashboard Dashboard `json:"dashboard,omitempty"`
diff --git a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
index a18cb43c3..84db3157b 100644
--- a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
@@ -1061,6 +1061,7 @@ func (in *TektonConfigSpec) DeepCopyInto(out *TektonConfigSpec) {
 	in.Hub.DeepCopyInto(&out.Hub)
 	in.Pipeline.DeepCopyInto(&out.Pipeline)
 	out.Trigger = in.Trigger
+	in.Chain.DeepCopyInto(&out.Chain)
 	out.Dashboard = in.Dashboard
 	if in.Params != nil {
 		in, out := &in.Params, &out.Params
diff --git a/pkg/reconciler/kubernetes/tektondashboard/reconcile.go b/pkg/reconciler/kubernetes/tektondashboard/reconcile.go
index 74795cabb..588dd55a7 100644
--- a/pkg/reconciler/kubernetes/tektondashboard/reconcile.go
+++ b/pkg/reconciler/kubernetes/tektondashboard/reconcile.go
@@ -108,6 +108,11 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, td *v1alpha1.TektonDashb
 	} else {
 		manifest = r.fullaccessManifest
 	}
+
+	// When Tekton Dashboard is insalled targetNamespace is getting updated with the OwnerRef as TektonDashboard
+	// and hence deleting the component in the integration tests, targetNamespace was getting deleted. Hence
+	// filtering out the namespace here
+	manifest = manifest.Filter(mf.Not(mf.ByKind("Namespace")))
 	if err := r.installerSetClient.MainSet(ctx, td, &manifest, filterAndTransform(r.extension)); err != nil {
 		msg := fmt.Sprintf("Main Reconcilation failed: %s", err.Error())
 		logger.Error(msg)
diff --git a/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go b/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go
index 96b7d9f08..ea1913d31 100644
--- a/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go
+++ b/pkg/reconciler/kubernetes/tektonpipeline/reconcile.go
@@ -98,7 +98,13 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tp *v1alpha1.TektonPipel
 	// Mark PreReconcile Complete
 	tp.Status.MarkPreReconcilerComplete()
 
-	if err := r.installerSetClient.MainSet(ctx, tp, &r.manifest, filterAndTransform(r.extension)); err != nil {
+	// When TektonPipeline component is deleted targetNamespace was getting deleted,
+	// because in pipeline reconciler targetNamespace was updated by adding few labels which
+	// in turn also updated the ownerRef of targetNamespace from TektonConfig to TektonPipeline.
+	// Since namespace is created in TektonConfig reconciler hence deleting TektonPipeline
+	// component should not delete the targetNamespace hence filtering out the namespace here
+	manifest := r.manifest.Filter(mf.Not(mf.ByKind("Namespace")))
+	if err := r.installerSetClient.MainSet(ctx, tp, &manifest, filterAndTransform(r.extension)); err != nil {
 		msg := fmt.Sprintf("Main Reconcilation failed: %s", err.Error())
 		logger.Error(msg)
 		if err == v1alpha1.REQUEUE_EVENT_AFTER {
diff --git a/pkg/reconciler/openshift/tektonpipeline/extension.go b/pkg/reconciler/openshift/tektonpipeline/extension.go
index 5d9dd10c2..196006594 100644
--- a/pkg/reconciler/openshift/tektonpipeline/extension.go
+++ b/pkg/reconciler/openshift/tektonpipeline/extension.go
@@ -77,6 +77,9 @@ func (oe openshiftExtension) PreReconcile(ctx context.Context, comp v1alpha1.Tek
 	if err != nil {
 		return err
 	}
+
+	// Filtering out the namespace because it add TektonPipeline as OwnerRef in targetNamespace
+	*manifest = manifest.Filter(mf.Not(mf.ByKind("Namespace")))
 	if err := oe.installerSetClient.PreSet(ctx, comp, manifest, filterAndTransform()); err != nil {
 		return err
 	}
diff --git a/pkg/reconciler/shared/tektonconfig/chain/chain.go b/pkg/reconciler/shared/tektonconfig/chain/chain.go
new file mode 100644
index 000000000..c6b4864cc
--- /dev/null
+++ b/pkg/reconciler/shared/tektonconfig/chain/chain.go
@@ -0,0 +1,151 @@
+/*
+Copyright 2023 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 chain
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+	op "github.com/tektoncd/operator/pkg/client/clientset/versioned/typed/operator/v1alpha1"
+	apierrs "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"knative.dev/pkg/apis"
+)
+
+func EnsureTektonChainExists(ctx context.Context, clients op.TektonChainInterface, tc *v1alpha1.TektonChain) (*v1alpha1.TektonChain, error) {
+	tcCR, err := GetChain(ctx, clients, v1alpha1.ChainResourceName)
+
+	if err != nil {
+		if !apierrs.IsNotFound(err) {
+			return nil, err
+		}
+		if err := CreateChain(ctx, clients, tc); err != nil {
+			return nil, err
+		}
+		return nil, v1alpha1.RECONCILE_AGAIN_ERR
+	}
+
+	tcCR, err = UpdateChain(ctx, tcCR, tc, clients)
+	if err != nil {
+		return nil, err
+	}
+
+	ready, err := isTektonChainReady(tcCR)
+	if err != nil {
+		return nil, err
+	}
+	if !ready {
+		return nil, v1alpha1.RECONCILE_AGAIN_ERR
+	}
+
+	return tcCR, err
+}
+
+func EnsureTektonChainCRNotExists(ctx context.Context, clients op.TektonChainInterface) error {
+	if _, err := GetChain(ctx, clients, v1alpha1.ChainResourceName); err != nil {
+		if apierrs.IsNotFound(err) {
+			// TektonChain CR is gone, hence return nil
+			return nil
+		}
+		return err
+	}
+	// if the Get was successful, try deleting the CR
+	if err := clients.Delete(ctx, v1alpha1.ChainResourceName, metav1.DeleteOptions{}); err != nil {
+		if apierrs.IsNotFound(err) {
+			// TektonChain CR is gone, hence return nil
+			return nil
+		}
+		return fmt.Errorf("TektonChain %q failed to delete: %v", v1alpha1.ChainResourceName, err)
+	}
+	// if the Delete API call was success,
+	// then return requeue_event
+	// so that in a subsequent reconcile call the absence of the CR is verified by one of the 2 checks above
+	return v1alpha1.RECONCILE_AGAIN_ERR
+}
+
+func GetChain(ctx context.Context, clients op.TektonChainInterface, name string) (*v1alpha1.TektonChain, error) {
+	return clients.Get(ctx, name, metav1.GetOptions{})
+}
+
+func CreateChain(ctx context.Context, clients op.TektonChainInterface, tt *v1alpha1.TektonChain) error {
+	_, err := clients.Create(ctx, tt, metav1.CreateOptions{})
+	return err
+}
+
+func UpdateChain(ctx context.Context, old *v1alpha1.TektonChain, new *v1alpha1.TektonChain, clients op.TektonChainInterface) (*v1alpha1.TektonChain, error) {
+	// if the chain spec is changed then update the instance
+	updated := false
+
+	if new.Spec.TargetNamespace != old.Spec.TargetNamespace {
+		old.Spec.TargetNamespace = new.Spec.TargetNamespace
+		updated = true
+	}
+
+	if !reflect.DeepEqual(old.Spec.Chain, new.Spec.Chain) {
+		old.Spec.Chain = new.Spec.Chain
+		updated = true
+	}
+
+	if !reflect.DeepEqual(old.Spec.Config, new.Spec.Config) {
+		old.Spec.Config = new.Spec.Config
+		updated = true
+	}
+
+	if old.ObjectMeta.OwnerReferences == nil {
+		old.ObjectMeta.OwnerReferences = new.ObjectMeta.OwnerReferences
+		updated = true
+	}
+
+	if updated {
+		_, err := clients.Update(ctx, old, metav1.UpdateOptions{})
+		if err != nil {
+			return nil, err
+		}
+		return nil, v1alpha1.RECONCILE_AGAIN_ERR
+	}
+	return old, nil
+}
+
+// isTektonChainReady will check the status conditions of the TektonChain and return true if the TektonChain is ready.
+func isTektonChainReady(s *v1alpha1.TektonChain) (bool, error) {
+	if s.GetStatus() != nil && s.GetStatus().GetCondition(apis.ConditionReady) != nil {
+		if strings.Contains(s.GetStatus().GetCondition(apis.ConditionReady).Message, v1alpha1.UpgradePending) {
+			return false, v1alpha1.DEPENDENCY_UPGRADE_PENDING_ERR
+		}
+	}
+	return s.Status.IsReady(), nil
+}
+
+func GetTektonChainCR(config *v1alpha1.TektonConfig) *v1alpha1.TektonChain {
+	ownerRef := *metav1.NewControllerRef(config, config.GroupVersionKind())
+	return &v1alpha1.TektonChain{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            v1alpha1.ChainResourceName,
+			OwnerReferences: []metav1.OwnerReference{ownerRef},
+		},
+		Spec: v1alpha1.TektonChainSpec{
+			CommonSpec: v1alpha1.CommonSpec{
+				TargetNamespace: config.Spec.TargetNamespace,
+			},
+			Config: config.Spec.Config,
+			Chain:  config.Spec.Chain,
+		},
+	}
+}
diff --git a/pkg/reconciler/shared/tektonconfig/controller.go b/pkg/reconciler/shared/tektonconfig/controller.go
index 45dd4f159..40c3ddc33 100644
--- a/pkg/reconciler/shared/tektonconfig/controller.go
+++ b/pkg/reconciler/shared/tektonconfig/controller.go
@@ -26,6 +26,7 @@ import (
 	mf "github.com/manifestival/manifestival"
 	"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
 	operatorclient "github.com/tektoncd/operator/pkg/client/injection/client"
+	tektonChaininformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonchain"
 	tektonConfiginformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonconfig"
 	tektonInstallerinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektoninstallerset"
 	tektonPipelineinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonpipeline"
@@ -88,6 +89,11 @@ func NewExtensibleController(generator common.ExtensionGenerator) injection.Cont
 			Handler:    controller.HandleAll(impl.EnqueueControllerOf),
 		})
 
+		tektonChaininformer.Get(ctx).Informer().AddEventHandler(cache.FilteringResourceEventHandler{
+			FilterFunc: controller.FilterController(&v1alpha1.TektonConfig{}),
+			Handler:    controller.HandleAll(impl.EnqueueControllerOf),
+		})
+
 		tektonInstallerinformer.Get(ctx).Informer().AddEventHandler(cache.FilteringResourceEventHandler{
 			FilterFunc: controller.FilterController(&v1alpha1.TektonConfig{}),
 			Handler:    controller.HandleAll(impl.EnqueueControllerOf),
diff --git a/pkg/reconciler/shared/tektonconfig/tektonconfig.go b/pkg/reconciler/shared/tektonconfig/tektonconfig.go
index fe4465bac..a001dcbc8 100644
--- a/pkg/reconciler/shared/tektonconfig/tektonconfig.go
+++ b/pkg/reconciler/shared/tektonconfig/tektonconfig.go
@@ -25,6 +25,7 @@ import (
 	clientset "github.com/tektoncd/operator/pkg/client/clientset/versioned"
 	tektonConfigreconciler "github.com/tektoncd/operator/pkg/client/injection/reconciler/operator/v1alpha1/tektonconfig"
 	"github.com/tektoncd/operator/pkg/reconciler/common"
+	"github.com/tektoncd/operator/pkg/reconciler/shared/tektonconfig/chain"
 	"github.com/tektoncd/operator/pkg/reconciler/shared/tektonconfig/pipeline"
 	"github.com/tektoncd/operator/pkg/reconciler/shared/tektonconfig/trigger"
 	"k8s.io/apimachinery/pkg/api/errors"
@@ -66,6 +67,9 @@ func (r *Reconciler) FinalizeKind(ctx context.Context, original *v1alpha1.Tekton
 		if err := trigger.EnsureTektonTriggerCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonTriggers()); err != nil {
 			return err
 		}
+		if err := chain.EnsureTektonChainCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonChains()); err != nil {
+			return err
+		}
 		if err := pipeline.EnsureTektonPipelineCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonPipelines()); err != nil {
 			return err
 		}
@@ -143,12 +147,25 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonConfi
 		if _, err := trigger.EnsureTektonTriggerExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonTriggers(), tektontrigger); err != nil {
 			tc.Status.MarkComponentNotReady(fmt.Sprintf("TektonTrigger: %s", err.Error()))
 			return v1alpha1.REQUEUE_EVENT_AFTER
+
+		}
+
+		tektonchain := chain.GetTektonChainCR(tc)
+		if _, err := chain.EnsureTektonChainExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonChains(), tektonchain); err != nil {
+			tc.Status.MarkComponentNotReady(fmt.Sprintf("TektonChain: %s", err.Error()))
+			return v1alpha1.REQUEUE_EVENT_AFTER
 		}
+
 	} else {
 		if err := trigger.EnsureTektonTriggerCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonTriggers()); err != nil {
 			tc.Status.MarkComponentNotReady(fmt.Sprintf("TektonTrigger: %s", err.Error()))
 			return v1alpha1.REQUEUE_EVENT_AFTER
 		}
+
+		if err := chain.EnsureTektonChainCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonChains()); err != nil {
+			tc.Status.MarkComponentNotReady(fmt.Sprintf("TektonChain: %s", err.Error()))
+			return v1alpha1.REQUEUE_EVENT_AFTER
+		}
 	}
 
 	// reconcile pruner installerSet


Test patch:
diff --git a/pkg/reconciler/shared/tektonconfig/chain/chain_test.go b/pkg/reconciler/shared/tektonconfig/chain/chain_test.go
new file mode 100644
index 000000000..5ab6a0f97
--- /dev/null
+++ b/pkg/reconciler/shared/tektonconfig/chain/chain_test.go
@@ -0,0 +1,142 @@
+/*
+Copyright 2023 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 chain
+
+import (
+	"context"
+	"testing"
+
+	op "github.com/tektoncd/operator/pkg/client/clientset/versioned/typed/operator/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
+
+	"github.com/tektoncd/operator/pkg/client/injection/client/fake"
+	util "github.com/tektoncd/operator/pkg/reconciler/common/testing"
+	ts "knative.dev/pkg/reconciler/testing"
+)
+
+func TestEnsureTektonChainExists(t *testing.T) {
+	ctx, _, _ := ts.SetupFakeContextWithCancel(t)
+	c := fake.Get(ctx)
+	tt := GetTektonChainCR(getTektonConfig())
+
+	// first invocation should create instance as it is non-existent and return RECONCILE_AGAIN_ERR
+	_, err := EnsureTektonChainExists(ctx, c.OperatorV1alpha1().TektonChains(), tt)
+	util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR)
+
+	// during second invocation instance exists but waiting on dependencies (pipeline, Chains)
+	// hence returns RECONCILE_AGAIN_ERR
+	_, err = EnsureTektonChainExists(ctx, c.OperatorV1alpha1().TektonChains(), tt)
+	util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR)
+
+	// make upgrade checks pass
+	makeUpgradeCheckPass(t, ctx, c.OperatorV1alpha1().TektonChains())
+
+	// next invocation should return RECONCILE_AGAIN_ERR as Dashboard is waiting for installation (prereconcile, postreconcile, installersets...)
+	_, err = EnsureTektonChainExists(ctx, c.OperatorV1alpha1().TektonChains(), tt)
+	util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR)
+
+	// mark the instance ready
+	markChainsReady(t, ctx, c.OperatorV1alpha1().TektonChains())
+
+	// next invocation should return nil error as the instance is ready
+	_, err = EnsureTektonChainExists(ctx, c.OperatorV1alpha1().TektonChains(), tt)
+	util.AssertEqual(t, err, nil)
+
+	// test update propagation from tektonConfig
+	tt.Spec.TargetNamespace = "foobar"
+	_, err = EnsureTektonChainExists(ctx, c.OperatorV1alpha1().TektonChains(), tt)
+	util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR)
+
+	_, err = EnsureTektonChainExists(ctx, c.OperatorV1alpha1().TektonChains(), tt)
+	util.AssertEqual(t, err, nil)
+}
+
+func TestEnsureTektonChainCRNotExists(t *testing.T) {
+	ctx, _, _ := ts.SetupFakeContextWithCancel(t)
+	c := fake.Get(ctx)
+
+	// when no instance exists, nil error is returned immediately
+	err := EnsureTektonChainCRNotExists(ctx, c.OperatorV1alpha1().TektonChains())
+	util.AssertEqual(t, err, nil)
+
+	// create an instance for testing other cases
+	tt := GetTektonChainCR(getTektonConfig())
+	_, err = EnsureTektonChainExists(ctx, c.OperatorV1alpha1().TektonChains(), tt)
+	util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR)
+
+	// when an instance exists the first invocation should make the delete API call and
+	// return RECONCILE_AGAIN_ERROR. So that the deletion can be confirmed in a subsequent invocation
+	err = EnsureTektonChainCRNotExists(ctx, c.OperatorV1alpha1().TektonChains())
+	util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR)
+
+	// when the instance is completely removed from a cluster, the function should return nil error
+	err = EnsureTektonChainCRNotExists(ctx, c.OperatorV1alpha1().TektonChains())
+	util.AssertEqual(t, err, nil)
+}
+
+func markChainsReady(t *testing.T, ctx context.Context, c op.TektonChainInterface) {
+	t.Helper()
+	tr, err := c.Get(ctx, v1alpha1.ChainResourceName, metav1.GetOptions{})
+	util.AssertEqual(t, err, nil)
+	tr.Status.MarkDependenciesInstalled()
+	tr.Status.MarkPreReconcilerComplete()
+	tr.Status.MarkInstallerSetAvailable()
+	tr.Status.MarkInstallerSetReady()
+	tr.Status.MarkPostReconcilerComplete()
+	_, err = c.UpdateStatus(ctx, tr, metav1.UpdateOptions{})
+	util.AssertEqual(t, err, nil)
+}
+
+func makeUpgradeCheckPass(t *testing.T, ctx context.Context, c op.TektonChainInterface) {
+	t.Helper()
+	// set necessary version labels to make upgrade check pass
+	chain, err := c.Get(ctx, v1alpha1.ChainResourceName, metav1.GetOptions{})
+	util.AssertEqual(t, err, nil)
+	setDummyVersionLabel(t, chain)
+	_, err = c.Update(ctx, chain, metav1.UpdateOptions{})
+	util.AssertEqual(t, err, nil)
+}
+
+func setDummyVersionLabel(t *testing.T, tr *v1alpha1.TektonChain) {
+	t.Helper()
+
+	oprVersion := "v1.2.3"
+	t.Setenv(v1alpha1.VersionEnvKey, oprVersion)
+
+	labels := tr.GetLabels()
+	if labels == nil {
+		labels = map[string]string{}
+	}
+	labels[v1alpha1.ReleaseVersionKey] = oprVersion
+	tr.SetLabels(labels)
+}
+
+func getTektonConfig() *v1alpha1.TektonConfig {
+	return &v1alpha1.TektonConfig{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: v1alpha1.ConfigResourceName,
+		},
+		Spec: v1alpha1.TektonConfigSpec{
+			Profile: v1alpha1.ProfileAll,
+			CommonSpec: v1alpha1.CommonSpec{
+				TargetNamespace: "tekton-pipelines",
+			},
+		},
+	}
+}