diff --git a/pkg/reconciler/common/common.go b/pkg/reconciler/common/common.go index d645165b1a..df48c1d22f 100644 --- a/pkg/reconciler/common/common.go +++ b/pkg/reconciler/common/common.go @@ -19,7 +19,6 @@ package common import ( "context" "fmt" - "time" "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" informer "github.com/tektoncd/operator/pkg/client/informers/externalversions/operator/v1alpha1" @@ -27,8 +26,6 @@ import ( ) var ( - Interval = 10 * time.Second - Timeout = 1 * time.Minute // DefaultSA is the default service account DefaultSA = "pipeline" ) diff --git a/pkg/reconciler/kubernetes/tektonconfig/extension.go b/pkg/reconciler/kubernetes/tektonconfig/extension.go index f62311bafb..f6b1f759cf 100644 --- a/pkg/reconciler/kubernetes/tektonconfig/extension.go +++ b/pkg/reconciler/kubernetes/tektonconfig/extension.go @@ -55,7 +55,7 @@ func (oe kubernetesExtension) PostReconcile(ctx context.Context, comp v1alpha1.T } if configInstance.Spec.Profile == v1alpha1.ProfileLite || configInstance.Spec.Profile == v1alpha1.ProfileBasic { - return extension.TektonDashboardCRDelete(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonDashboards(), v1alpha1.DashboardResourceName) + return extension.EnsureTektonDashboardCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonDashboards()) } return nil @@ -63,7 +63,7 @@ func (oe kubernetesExtension) PostReconcile(ctx context.Context, comp v1alpha1.T func (oe kubernetesExtension) Finalize(ctx context.Context, comp v1alpha1.TektonComponent) error { configInstance := comp.(*v1alpha1.TektonConfig) if configInstance.Spec.Profile == v1alpha1.ProfileAll { - return extension.TektonDashboardCRDelete(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonDashboards(), v1alpha1.DashboardResourceName) + return extension.EnsureTektonDashboardCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonDashboards()) } return nil } diff --git a/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard.go b/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard.go index d72ffdba67..d384b85e8d 100644 --- a/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard.go +++ b/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard.go @@ -18,7 +18,6 @@ package extension import ( "context" - "errors" "fmt" "reflect" @@ -27,7 +26,6 @@ import ( "github.com/tektoncd/operator/pkg/reconciler/common" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" ) func EnsureTektonDashboardExists(ctx context.Context, clients op.TektonDashboardInterface, config *v1alpha1.TektonConfig) (*v1alpha1.TektonDashboard, error) { @@ -108,7 +106,11 @@ func updateDashboard(ctx context.Context, tdCR *v1alpha1.TektonDashboard, config } if updated { - return clients.Update(ctx, tdCR, metav1.UpdateOptions{}) + _, err := clients.Update(ctx, tdCR, metav1.UpdateOptions{}) + if err != nil { + return nil, err + } + return nil, v1alpha1.RECONCILE_AGAIN_ERR } return tdCR, nil @@ -126,37 +128,26 @@ func isTektonDashboardReady(s *v1alpha1.TektonDashboard, err error) (bool, error return s.Status.IsReady(), err } -// TektonDashboardCRDelete deletes tha TektonDashboard to see if all resources will be deleted -func TektonDashboardCRDelete(ctx context.Context, clients op.TektonDashboardInterface, name string) error { +// EnsureTektonDashboardCRNotExists deletes the singleton instance of TektonDashboard +// and ensures the instance is removed checking whether in exists in a subsequent invocation +func EnsureTektonDashboardCRNotExists(ctx context.Context, clients op.TektonDashboardInterface) error { if _, err := GetDashboard(ctx, clients, v1alpha1.DashboardResourceName); err != nil { if apierrs.IsNotFound(err) { + // TektonDashBoard CR is gone, hence return nil return nil } return err } - if err := clients.Delete(ctx, name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("TektonDashboard %q failed to delete: %v", name, err) - } - err := wait.PollImmediate(common.Interval, common.Timeout, func() (bool, error) { - _, err := clients.Get(ctx, name, metav1.GetOptions{}) + // if the Get was successful, try deleting the CR + if err := clients.Delete(ctx, v1alpha1.DashboardResourceName, metav1.DeleteOptions{}); err != nil { if apierrs.IsNotFound(err) { - return true, nil + // TektonDashBoard CR is gone, hence return nil + return nil } - return false, err - }) - if err != nil { - return fmt.Errorf("Timed out waiting on TektonDashboard to delete %v", err) - } - return verifyNoTektonDashboardCR(ctx, clients) -} - -func verifyNoTektonDashboardCR(ctx context.Context, clients op.TektonDashboardInterface) error { - dashboards, err := clients.List(ctx, metav1.ListOptions{}) - if err != nil { - return err - } - if len(dashboards.Items) > 0 { - return errors.New("Unable to verify cluster-scoped resources are deleted if any TektonDashboard exists") + return fmt.Errorf("TektonDashboard %q failed to delete: %v", v1alpha1.DashboardResourceName, err) } - return nil + // 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 } diff --git a/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard_test.go b/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard_test.go index bbd10d34a1..8564f6ba0c 100644 --- a/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard_test.go +++ b/pkg/reconciler/kubernetes/tektonconfig/extension/dashboard_test.go @@ -17,8 +17,12 @@ limitations under the License. package extension import ( + "context" + "os" "testing" + op "github.com/tektoncd/operator/pkg/client/clientset/versioned/typed/operator/v1alpha1" + "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" @@ -27,53 +31,97 @@ import ( ts "knative.dev/pkg/reconciler/testing" ) -func TestTektonDashboardCreateAndDeleteCR(t *testing.T) { +func TestEnsureTektonDashbordExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) tConfig := pipeline.GetTektonConfig() + + // first invocation should create instance as it is non-existent and return RECONCILE_AGAIN_ERR _, err := EnsureTektonDashboardExists(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) - util.AssertNotEqual(t, err, nil) - err = TektonDashboardCRDelete(ctx, c.OperatorV1alpha1().TektonDashboards(), v1alpha1.DashboardResourceName) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // during second invocation instance exists but waiting on dependencies (pipeline, triggers) + // hence returns DEPENDENCY_UPGRADE_PENDING_ERR + _, err = EnsureTektonDashboardExists(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) + util.AssertEqual(t, err, v1alpha1.DEPENDENCY_UPGRADE_PENDING_ERR) + + // make upgrade checks pass + makeUpgradeCheckPass(t, ctx, c.OperatorV1alpha1().TektonDashboards()) + + // next invocation should return RECONCILE_AGAIN_ERR as Dashboard is waiting for installation (prereconcile, postreconcile, installersets...) + _, err = EnsureTektonDashboardExists(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // mark the instance ready + markDashboardsReady(t, ctx, c.OperatorV1alpha1().TektonDashboards()) + + // next invocation should return nil error as the instance is ready + _, err = EnsureTektonDashboardExists(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) + util.AssertEqual(t, err, nil) + + // test update propagation from tektonConfig + tConfig.Spec.TargetNamespace = "foobar" + _, err = EnsureTektonDashboardExists(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + _, err = EnsureTektonDashboardExists(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) util.AssertEqual(t, err, nil) } -func TestTektonDashboardUpdate(t *testing.T) { +func TestEnsureTektonDashboardCRNotExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) - tConfig := pipeline.GetTektonConfig() - _, err := createDashboard(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) + + // when no instance exists, nil error is returned immediately + err := EnsureTektonDashboardCRNotExists(ctx, c.OperatorV1alpha1().TektonDashboards()) util.AssertEqual(t, err, nil) - // to update dashboard instance - tConfig = &v1alpha1.TektonConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: v1alpha1.ConfigResourceName, - }, - Spec: v1alpha1.TektonConfigSpec{ - Profile: "all", - CommonSpec: v1alpha1.CommonSpec{ - TargetNamespace: "tekton-pipelines1", - }, - Dashboard: v1alpha1.Dashboard{ - DashboardProperties: v1alpha1.DashboardProperties{ - Readonly: false, - }, - }, - Config: v1alpha1.Config{ - NodeSelector: map[string]string{ - "key": "value", - }, - }, - }, - } + + // create an instance for testing other cases + tConfig := pipeline.GetTektonConfig() _, err = EnsureTektonDashboardExists(ctx, c.OperatorV1alpha1().TektonDashboards(), tConfig) - util.AssertNotEqual(t, err, nil) - err = TektonDashboardCRDelete(ctx, c.OperatorV1alpha1().TektonDashboards(), v1alpha1.DashboardResourceName) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when an instance exists the first invoacation should make the delete API call and + // return RECONCILE_AGAI_ERROR. So that the deletion can be confirmed in a subsequent invocation + err = EnsureTektonDashboardCRNotExists(ctx, c.OperatorV1alpha1().TektonDashboards()) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when the instance is completely removed from a cluster, the function should return nil error + err = EnsureTektonDashboardCRNotExists(ctx, c.OperatorV1alpha1().TektonDashboards()) util.AssertEqual(t, err, nil) } -func TestTektonDashboardCRDelete(t *testing.T) { - ctx, _, _ := ts.SetupFakeContextWithCancel(t) - c := fake.Get(ctx) - err := TektonDashboardCRDelete(ctx, c.OperatorV1alpha1().TektonDashboards(), v1alpha1.DashboardResourceName) +func markDashboardsReady(t *testing.T, ctx context.Context, c op.TektonDashboardInterface) { + t.Helper() + td, err := c.Get(ctx, v1alpha1.DashboardResourceName, metav1.GetOptions{}) + util.AssertEqual(t, err, nil) + td.Status.MarkDependenciesInstalled() + td.Status.MarkPreReconcilerComplete() + td.Status.MarkInstallerSetAvailable() + td.Status.MarkInstallerSetReady() + td.Status.MarkPostReconcilerComplete() + _, err = c.UpdateStatus(ctx, td, metav1.UpdateOptions{}) util.AssertEqual(t, err, nil) } + +func makeUpgradeCheckPass(t *testing.T, ctx context.Context, c op.TektonDashboardInterface) { + t.Helper() + // set necessary version labels to make upgrade check pass + dashboard, err := c.Get(ctx, v1alpha1.DashboardResourceName, metav1.GetOptions{}) + util.AssertEqual(t, err, nil) + setDummyVersionLabel(dashboard) + _, err = c.Update(ctx, dashboard, metav1.UpdateOptions{}) + util.AssertEqual(t, err, nil) +} + +func setDummyVersionLabel(td *v1alpha1.TektonDashboard) { + oprVersion := "v1.2.3" + os.Setenv(v1alpha1.VersionEnvKey, oprVersion) + + labels := td.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[v1alpha1.ReleaseVersionKey] = oprVersion + td.SetLabels(labels) +} diff --git a/pkg/reconciler/openshift/tektonconfig/extension.go b/pkg/reconciler/openshift/tektonconfig/extension.go index 166a8172b4..7fde91b799 100644 --- a/pkg/reconciler/openshift/tektonconfig/extension.go +++ b/pkg/reconciler/openshift/tektonconfig/extension.go @@ -108,7 +108,7 @@ func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.Te } if configInstance.Spec.Profile == v1alpha1.ProfileLite || configInstance.Spec.Profile == v1alpha1.ProfileBasic { - return extension.TektonAddonCRDelete(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonAddons(), v1alpha1.AddonResourceName) + return extension.EnsureTektonAddonCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonAddons()) } return nil @@ -116,7 +116,7 @@ func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.Te func (oe openshiftExtension) Finalize(ctx context.Context, comp v1alpha1.TektonComponent) error { configInstance := comp.(*v1alpha1.TektonConfig) if configInstance.Spec.Profile == v1alpha1.ProfileAll { - if err := extension.TektonAddonCRDelete(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonAddons(), v1alpha1.AddonResourceName); err != nil { + if err := extension.EnsureTektonAddonCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonAddons()); err != nil { return err } } diff --git a/pkg/reconciler/openshift/tektonconfig/extension/addon.go b/pkg/reconciler/openshift/tektonconfig/extension/addon.go index 5516fca28b..02f25042e4 100644 --- a/pkg/reconciler/openshift/tektonconfig/extension/addon.go +++ b/pkg/reconciler/openshift/tektonconfig/extension/addon.go @@ -18,7 +18,6 @@ package extension import ( "context" - "errors" "fmt" "reflect" @@ -27,7 +26,6 @@ import ( "github.com/tektoncd/operator/pkg/reconciler/common" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" ) func EnsureTektonAddonExists(ctx context.Context, clients op.TektonAddonInterface, config *v1alpha1.TektonConfig) (*v1alpha1.TektonAddon, error) { @@ -114,7 +112,11 @@ func updateAddon(ctx context.Context, taCR *v1alpha1.TektonAddon, config *v1alph } if updated { - return clients.Update(ctx, taCR, metav1.UpdateOptions{}) + _, err := clients.Update(ctx, taCR, metav1.UpdateOptions{}) + if err != nil { + return nil, err + } + return nil, v1alpha1.RECONCILE_AGAIN_ERR } return taCR, nil @@ -132,37 +134,24 @@ func isTektonAddonReady(s *v1alpha1.TektonAddon, err error) (bool, error) { return s.Status.IsReady(), err } -// TektonAddonCRDelete deletes tha TektonAddon to see if all resources will be deleted -func TektonAddonCRDelete(ctx context.Context, clients op.TektonAddonInterface, name string) error { +func EnsureTektonAddonCRNotExists(ctx context.Context, clients op.TektonAddonInterface) error { if _, err := GetAddon(ctx, clients, v1alpha1.AddonResourceName); err != nil { if apierrs.IsNotFound(err) { + // TektonAddon CR is gone, hence return nil return nil } return err } - if err := clients.Delete(ctx, name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("TektonAddon %q failed to delete: %v", name, err) - } - err := wait.PollImmediate(common.Interval, common.Timeout, func() (bool, error) { - _, err := clients.Get(ctx, name, metav1.GetOptions{}) + // if the Get was successful, try deleting the CR + if err := clients.Delete(ctx, v1alpha1.AddonResourceName, metav1.DeleteOptions{}); err != nil { if apierrs.IsNotFound(err) { - return true, nil + // TektonAddon CR is gone, hence return nil + return nil } - return false, err - }) - if err != nil { - return fmt.Errorf("Timed out waiting on TektonAddon to delete %v", err) - } - return verifyNoTektonAddonCR(ctx, clients) -} - -func verifyNoTektonAddonCR(ctx context.Context, clients op.TektonAddonInterface) error { - addons, err := clients.List(ctx, metav1.ListOptions{}) - if err != nil { - return err - } - if len(addons.Items) > 0 { - return errors.New("Unable to verify cluster-scoped resources are deleted if any TektonAddon exists") + return fmt.Errorf("TektonAddon %q failed to delete: %v", v1alpha1.AddonResourceName, err) } - return nil + // 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 } diff --git a/pkg/reconciler/openshift/tektonconfig/extension/addon_test.go b/pkg/reconciler/openshift/tektonconfig/extension/addon_test.go index 6f81f3e796..4dc76031c7 100644 --- a/pkg/reconciler/openshift/tektonconfig/extension/addon_test.go +++ b/pkg/reconciler/openshift/tektonconfig/extension/addon_test.go @@ -17,64 +17,112 @@ limitations under the License. package extension import ( + "context" + "os" "testing" + "github.com/tektoncd/operator/pkg/reconciler/shared/tektonconfig/pipeline" + + op "github.com/tektoncd/operator/pkg/client/clientset/versioned/typed/operator/v1alpha1" + "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" - "github.com/tektoncd/operator/pkg/reconciler/shared/tektonconfig/pipeline" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ts "knative.dev/pkg/reconciler/testing" ) -func TestTektonAddonCreateAndDeleteCR(t *testing.T) { +func TestEnsureTektonAddonCRExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) tConfig := pipeline.GetTektonConfig() + + // first invocation should create instance as it is non-existent and return RECONCILE_AGAIN_ERR _, err := EnsureTektonAddonExists(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) - util.AssertNotEqual(t, err, nil) - err = TektonAddonCRDelete(ctx, c.OperatorV1alpha1().TektonAddons(), v1alpha1.AddonResourceName) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // during second invocation instance exists but waiting on dependencies (pipeline, triggers) + // hence returns DEPENDENCY_UPGRADE_PENDING_ERR + _, err = EnsureTektonAddonExists(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) + util.AssertEqual(t, err, v1alpha1.DEPENDENCY_UPGRADE_PENDING_ERR) + + // make upgrade checks pass + makeUpgradeCheckPass(t, ctx, c.OperatorV1alpha1().TektonAddons()) + + // next invocation should return RECONCILE_AGAIN_ERR as Dashboard is waiting for installation (prereconcile, postreconcile, installersets...) + _, err = EnsureTektonAddonExists(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // mark the instance ready + markAddonsReady(t, ctx, c.OperatorV1alpha1().TektonAddons()) + + // next invocation should return nil error as the instance is ready + _, err = EnsureTektonAddonExists(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) + util.AssertEqual(t, err, nil) + + // test update propagation from tektonConfig + tConfig.Spec.TargetNamespace = "foobar" + _, err = EnsureTektonAddonExists(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + _, err = EnsureTektonAddonExists(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) util.AssertEqual(t, err, nil) } -func TestTektonDashboardUpdate(t *testing.T) { +func TestEnsureTektonAddonCRNotExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) - tConfig := pipeline.GetTektonConfig() - _, err := createAddon(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) + + // when no instance exists, nil error is returned immediately + err := EnsureTektonAddonCRNotExists(ctx, c.OperatorV1alpha1().TektonAddons()) util.AssertEqual(t, err, nil) - // to update addon instance - tConfig = &v1alpha1.TektonConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: v1alpha1.ConfigResourceName, - }, - Spec: v1alpha1.TektonConfigSpec{ - Profile: "all", - CommonSpec: v1alpha1.CommonSpec{ - TargetNamespace: "tekton-pipelines1", - }, - Addon: v1alpha1.Addon{ - Params: []v1alpha1.Param{{ - Name: "clusterTasks", - Value: "false", - }}, - }, - Config: v1alpha1.Config{ - NodeSelector: map[string]string{ - "key": "value", - }, - }, - }, - } + + // create an instance for testing other cases + tConfig := pipeline.GetTektonConfig() _, err = EnsureTektonAddonExists(ctx, c.OperatorV1alpha1().TektonAddons(), tConfig) - util.AssertNotEqual(t, err, nil) - err = TektonAddonCRDelete(ctx, c.OperatorV1alpha1().TektonAddons(), v1alpha1.AddonResourceName) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when an instance exists the first invoacation should make the delete API call and + // return RECONCILE_AGAI_ERROR. So that the deletion can be confirmed in a subsequent invocation + err = EnsureTektonAddonCRNotExists(ctx, c.OperatorV1alpha1().TektonAddons()) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when the instance is completely removed from a cluster, the function should return nil error + err = EnsureTektonAddonCRNotExists(ctx, c.OperatorV1alpha1().TektonAddons()) util.AssertEqual(t, err, nil) } -func TestTektonAddonCRDelete(t *testing.T) { - ctx, _, _ := ts.SetupFakeContextWithCancel(t) - c := fake.Get(ctx) - err := TektonAddonCRDelete(ctx, c.OperatorV1alpha1().TektonAddons(), v1alpha1.AddonResourceName) +func markAddonsReady(t *testing.T, ctx context.Context, c op.TektonAddonInterface) { + t.Helper() + ta, err := c.Get(ctx, v1alpha1.AddonResourceName, metav1.GetOptions{}) util.AssertEqual(t, err, nil) + ta.Status.MarkDependenciesInstalled() + ta.Status.MarkPreReconcilerComplete() + ta.Status.MarkInstallerSetReady() + ta.Status.MarkInstallerSetReady() + ta.Status.MarkPostReconcilerComplete() + _, err = c.UpdateStatus(ctx, ta, metav1.UpdateOptions{}) + util.AssertEqual(t, err, nil) +} + +func makeUpgradeCheckPass(t *testing.T, ctx context.Context, c op.TektonAddonInterface) { + t.Helper() + // set necessary version labels to make upgrade check pass + addon, err := c.Get(ctx, v1alpha1.AddonResourceName, metav1.GetOptions{}) + util.AssertEqual(t, err, nil) + setDummyVersionLabel(addon) + _, err = c.Update(ctx, addon, metav1.UpdateOptions{}) + util.AssertEqual(t, err, nil) +} + +func setDummyVersionLabel(ta *v1alpha1.TektonAddon) { + oprVersion := "v1.2.3" + os.Setenv(v1alpha1.VersionEnvKey, oprVersion) + + labels := ta.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[v1alpha1.ReleaseVersionKey] = oprVersion + ta.SetLabels(labels) } diff --git a/pkg/reconciler/shared/tektonconfig/pipeline/pipeline.go b/pkg/reconciler/shared/tektonconfig/pipeline/pipeline.go index 3bb58a79d6..d75457ad50 100644 --- a/pkg/reconciler/shared/tektonconfig/pipeline/pipeline.go +++ b/pkg/reconciler/shared/tektonconfig/pipeline/pipeline.go @@ -18,7 +18,6 @@ package pipeline import ( "context" - "errors" "fmt" "reflect" @@ -28,7 +27,6 @@ import ( "github.com/tektoncd/operator/pkg/reconciler/common" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" ) func EnsureTektonPipelineExists(ctx context.Context, clients op.TektonPipelineInterface, config *v1alpha1.TektonConfig) (*v1alpha1.TektonPipeline, error) { @@ -135,41 +133,6 @@ func isTektonPipelineReady(s *v1alpha1.TektonPipeline, err error) (bool, error) return s.Status.IsReady(), err } -// TektonPipelineCRDelete deletes tha TektonPipeline to see if all resources will be deleted -func TektonPipelineCRDelete(ctx context.Context, clients op.TektonPipelineInterface, name string) error { - if _, err := GetPipeline(ctx, clients, v1alpha1.PipelineResourceName); err != nil { - if apierrs.IsNotFound(err) { - return nil - } - return err - } - if err := clients.Delete(ctx, name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("TektonPipeline %q failed to delete: %v", name, err) - } - err := wait.PollImmediate(common.Interval, common.Timeout, func() (bool, error) { - _, err := clients.Get(ctx, name, metav1.GetOptions{}) - if apierrs.IsNotFound(err) { - return true, nil - } - return false, err - }) - if err != nil { - return fmt.Errorf("Timed out waiting on TektonPipeline to delete %v", err) - } - return verifyNoTektonPipelineCR(ctx, clients) -} - -func verifyNoTektonPipelineCR(ctx context.Context, clients op.TektonPipelineInterface) error { - pipelines, err := clients.List(ctx, metav1.ListOptions{}) - if err != nil { - return err - } - if len(pipelines.Items) > 0 { - return errors.New("TektonPipeline still exists") - } - return nil -} - func GetTektonConfig() *v1alpha1.TektonConfig { return &v1alpha1.TektonConfig{ ObjectMeta: metav1.ObjectMeta{ @@ -183,3 +146,25 @@ func GetTektonConfig() *v1alpha1.TektonConfig { }, } } + +func EnsureTektonPipelineCRNotExists(ctx context.Context, clients op.TektonPipelineInterface) error { + if _, err := GetPipeline(ctx, clients, v1alpha1.PipelineResourceName); err != nil { + if apierrs.IsNotFound(err) { + // TektonPipeline 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.PipelineResourceName, metav1.DeleteOptions{}); err != nil { + if apierrs.IsNotFound(err) { + // TektonPipeline CR is gone, hence return nil + return nil + } + return fmt.Errorf("TektonPipeline %q failed to delete: %v", v1alpha1.PipelineResourceName, 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 +} diff --git a/pkg/reconciler/shared/tektonconfig/pipeline/pipeline_test.go b/pkg/reconciler/shared/tektonconfig/pipeline/pipeline_test.go index 2d9a3948f0..1d796f5e62 100644 --- a/pkg/reconciler/shared/tektonconfig/pipeline/pipeline_test.go +++ b/pkg/reconciler/shared/tektonconfig/pipeline/pipeline_test.go @@ -17,27 +17,110 @@ limitations under the License. package pipeline import ( + "context" + "os" "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" + + op "github.com/tektoncd/operator/pkg/client/clientset/versioned/typed/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 TestTektonPipelineCreateAndDeleteCR(t *testing.T) { +func TestEnsureTektonPipelineExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) tConfig := GetTektonConfig() + + // first invocation should create instance as it is non-existent and return RECONCILE_AGAIN_ERR _, err := EnsureTektonPipelineExists(ctx, c.OperatorV1alpha1().TektonPipelines(), tConfig) - util.AssertEqual(t, err.Error(), "reconcile again and proceed") - err = TektonPipelineCRDelete(ctx, c.OperatorV1alpha1().TektonPipelines(), v1alpha1.PipelineResourceName) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // during second invocation instance exists but waiting on dependencies (pipeline, triggers) + // hence returns DEPENDENCY_UPGRADE_PENDING_ERR + _, err = EnsureTektonPipelineExists(ctx, c.OperatorV1alpha1().TektonPipelines(), tConfig) + util.AssertEqual(t, err, v1alpha1.DEPENDENCY_UPGRADE_PENDING_ERR) + + // make upgrade checks pass + makeUpgradeCheckPass(t, ctx, c.OperatorV1alpha1().TektonPipelines()) + + // next invocation should return RECONCILE_AGAIN_ERR as Dashboard is waiting for installation (prereconcile, postreconcile, installersets...) + _, err = EnsureTektonPipelineExists(ctx, c.OperatorV1alpha1().TektonPipelines(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // mark the instance ready + markPipelineReady(t, ctx, c.OperatorV1alpha1().TektonPipelines()) + + // next invocation should return nil error as the instance is ready + _, err = EnsureTektonPipelineExists(ctx, c.OperatorV1alpha1().TektonPipelines(), tConfig) + util.AssertEqual(t, err, nil) + + // test update propagation from tektonConfig + tConfig.Spec.TargetNamespace = "foobar" + _, err = EnsureTektonPipelineExists(ctx, c.OperatorV1alpha1().TektonPipelines(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + _, err = EnsureTektonPipelineExists(ctx, c.OperatorV1alpha1().TektonPipelines(), tConfig) util.AssertEqual(t, err, nil) } -func TestTektonPipelineCRDelete(t *testing.T) { +func TestEnsureTektonPipelineCRNotExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) - err := TektonPipelineCRDelete(ctx, c.OperatorV1alpha1().TektonPipelines(), v1alpha1.PipelineResourceName) + + // when no instance exists, nil error is returned immediately + err := EnsureTektonPipelineCRNotExists(ctx, c.OperatorV1alpha1().TektonPipelines()) + util.AssertEqual(t, err, nil) + + // create an instance for testing other cases + tConfig := GetTektonConfig() + _, err = EnsureTektonPipelineExists(ctx, c.OperatorV1alpha1().TektonPipelines(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when an instance exists the first invoacation should make the delete API call and + // return RECONCILE_AGAI_ERROR. So that the deletion can be confirmed in a subsequent invocation + err = EnsureTektonPipelineCRNotExists(ctx, c.OperatorV1alpha1().TektonPipelines()) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when the instance is completely removed from a cluster, the function should return nil error + err = EnsureTektonPipelineCRNotExists(ctx, c.OperatorV1alpha1().TektonPipelines()) + util.AssertEqual(t, err, nil) +} + +func markPipelineReady(t *testing.T, ctx context.Context, c op.TektonPipelineInterface) { + t.Helper() + tp, err := c.Get(ctx, v1alpha1.PipelineResourceName, metav1.GetOptions{}) + util.AssertEqual(t, err, nil) + tp.Status.MarkPreReconcilerComplete() + tp.Status.MarkInstallerSetAvailable() + tp.Status.MarkInstallerSetReady() + tp.Status.MarkPostReconcilerComplete() + _, err = c.UpdateStatus(ctx, tp, metav1.UpdateOptions{}) util.AssertEqual(t, err, nil) } + +func makeUpgradeCheckPass(t *testing.T, ctx context.Context, c op.TektonPipelineInterface) { + t.Helper() + // set necessary version labels to make upgrade check pass + pipeline, err := c.Get(ctx, v1alpha1.PipelineResourceName, metav1.GetOptions{}) + util.AssertEqual(t, err, nil) + setDummyVersionLabel(pipeline) + _, err = c.Update(ctx, pipeline, metav1.UpdateOptions{}) + util.AssertEqual(t, err, nil) +} + +func setDummyVersionLabel(tp *v1alpha1.TektonPipeline) { + oprVersion := "v1.2.3" + os.Setenv(v1alpha1.VersionEnvKey, oprVersion) + + labels := tp.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[v1alpha1.ReleaseVersionKey] = oprVersion + tp.SetLabels(labels) +} diff --git a/pkg/reconciler/shared/tektonconfig/tektonconfig.go b/pkg/reconciler/shared/tektonconfig/tektonconfig.go index 02a08056e9..16e7f2d1a2 100644 --- a/pkg/reconciler/shared/tektonconfig/tektonconfig.go +++ b/pkg/reconciler/shared/tektonconfig/tektonconfig.go @@ -60,13 +60,13 @@ func (r *Reconciler) FinalizeKind(ctx context.Context, original *v1alpha1.Tekton } if original.Spec.Profile == v1alpha1.ProfileLite { - return pipeline.TektonPipelineCRDelete(ctx, r.operatorClientSet.OperatorV1alpha1().TektonPipelines(), v1alpha1.PipelineResourceName) + return pipeline.EnsureTektonPipelineCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonPipelines()) } else { // TektonPipeline and TektonTrigger is common for profile type basic and all - if err := trigger.TektonTriggerCRDelete(ctx, r.operatorClientSet.OperatorV1alpha1().TektonTriggers(), v1alpha1.TriggerResourceName); err != nil { + if err := trigger.EnsureTektonTriggerCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonTriggers()); err != nil { return err } - if err := pipeline.TektonPipelineCRDelete(ctx, r.operatorClientSet.OperatorV1alpha1().TektonPipelines(), v1alpha1.PipelineResourceName); err != nil { + if err := pipeline.EnsureTektonPipelineCRNotExists(ctx, r.operatorClientSet.OperatorV1alpha1().TektonPipelines()); err != nil { return err } } @@ -127,7 +127,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tc *v1alpha1.TektonConfi return v1alpha1.REQUEUE_EVENT_AFTER } } else { - if err := trigger.TektonTriggerCRDelete(ctx, r.operatorClientSet.OperatorV1alpha1().TektonTriggers(), v1alpha1.TriggerResourceName); err != nil { + 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 } diff --git a/pkg/reconciler/shared/tektonconfig/trigger/trigger.go b/pkg/reconciler/shared/tektonconfig/trigger/trigger.go index e6901dd131..228173bc3d 100644 --- a/pkg/reconciler/shared/tektonconfig/trigger/trigger.go +++ b/pkg/reconciler/shared/tektonconfig/trigger/trigger.go @@ -18,7 +18,6 @@ package trigger import ( "context" - "errors" "fmt" "reflect" @@ -28,7 +27,6 @@ import ( "github.com/tektoncd/operator/pkg/reconciler/common" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" ) func EnsureTektonTriggerExists(ctx context.Context, clients op.TektonTriggerInterface, config *v1alpha1.TektonConfig) (*v1alpha1.TektonTrigger, error) { @@ -135,41 +133,6 @@ func isTektonTriggerReady(s *v1alpha1.TektonTrigger, err error) (bool, error) { return s.Status.IsReady(), err } -// TektonTriggerCRDelete deletes tha TektonTrigger to see if all resources will be deleted -func TektonTriggerCRDelete(ctx context.Context, clients op.TektonTriggerInterface, name string) error { - if _, err := GetTrigger(ctx, clients, v1alpha1.TriggerResourceName); err != nil { - if apierrs.IsNotFound(err) { - return nil - } - return err - } - if err := clients.Delete(ctx, name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("TektonTrigger %q failed to delete: %v", name, err) - } - err := wait.PollImmediate(common.Interval, common.Timeout, func() (bool, error) { - _, err := clients.Get(ctx, name, metav1.GetOptions{}) - if apierrs.IsNotFound(err) { - return true, nil - } - return false, err - }) - if err != nil { - return fmt.Errorf("Timed out waiting on TektonTrigger to delete %v", err) - } - return verifyNoTektonTriggerCR(ctx, clients) -} - -func verifyNoTektonTriggerCR(ctx context.Context, clients op.TektonTriggerInterface) error { - triggers, err := clients.List(ctx, metav1.ListOptions{}) - if err != nil { - return err - } - if len(triggers.Items) > 0 { - return errors.New("Unable to verify cluster-scoped resources are deleted if any TektonTrigger exists") - } - return nil -} - func GetTektonConfig() *v1alpha1.TektonConfig { return &v1alpha1.TektonConfig{ ObjectMeta: metav1.ObjectMeta{ @@ -183,3 +146,25 @@ func GetTektonConfig() *v1alpha1.TektonConfig { }, } } + +func EnsureTektonTriggerCRNotExists(ctx context.Context, clients op.TektonTriggerInterface) error { + if _, err := GetTrigger(ctx, clients, v1alpha1.TriggerResourceName); err != nil { + if apierrs.IsNotFound(err) { + // TektonTrigger 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.TriggerResourceName, metav1.DeleteOptions{}); err != nil { + if apierrs.IsNotFound(err) { + // TektonTrigger CR is gone, hence return nil + return nil + } + return fmt.Errorf("TektonTrigger %q failed to delete: %v", v1alpha1.TriggerResourceName, 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 +} diff --git a/pkg/reconciler/shared/tektonconfig/trigger/trigger_test.go b/pkg/reconciler/shared/tektonconfig/trigger/trigger_test.go index 39db2f62b2..a79c26fe83 100644 --- a/pkg/reconciler/shared/tektonconfig/trigger/trigger_test.go +++ b/pkg/reconciler/shared/tektonconfig/trigger/trigger_test.go @@ -17,27 +17,111 @@ limitations under the License. package trigger import ( + "context" + "os" "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 TestTektonTriggerCreateAndDeleteCR(t *testing.T) { +func TestEnsureTektonTriggerExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) tConfig := GetTektonConfig() + + // first invocation should create instance as it is non-existent and return RECONCILE_AGAIN_ERR _, err := EnsureTektonTriggerExists(ctx, c.OperatorV1alpha1().TektonTriggers(), tConfig) - util.AssertEqual(t, err.Error(), "reconcile again and proceed") - err = TektonTriggerCRDelete(ctx, c.OperatorV1alpha1().TektonTriggers(), v1alpha1.TriggerResourceName) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // during second invocation instance exists but waiting on dependencies (pipeline, triggers) + // hence returns DEPENDENCY_UPGRADE_PENDING_ERR + _, err = EnsureTektonTriggerExists(ctx, c.OperatorV1alpha1().TektonTriggers(), tConfig) + util.AssertEqual(t, err, v1alpha1.DEPENDENCY_UPGRADE_PENDING_ERR) + + // make upgrade checks pass + makeUpgradeCheckPass(t, ctx, c.OperatorV1alpha1().TektonTriggers()) + + // next invocation should return RECONCILE_AGAIN_ERR as Dashboard is waiting for installation (prereconcile, postreconcile, installersets...) + _, err = EnsureTektonTriggerExists(ctx, c.OperatorV1alpha1().TektonTriggers(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // mark the instance ready + markTriggersReady(t, ctx, c.OperatorV1alpha1().TektonTriggers()) + + // next invocation should return nil error as the instance is ready + _, err = EnsureTektonTriggerExists(ctx, c.OperatorV1alpha1().TektonTriggers(), tConfig) + util.AssertEqual(t, err, nil) + + // test update propagation from tektonConfig + tConfig.Spec.TargetNamespace = "foobar" + _, err = EnsureTektonTriggerExists(ctx, c.OperatorV1alpha1().TektonTriggers(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + _, err = EnsureTektonTriggerExists(ctx, c.OperatorV1alpha1().TektonTriggers(), tConfig) util.AssertEqual(t, err, nil) } -func TestTektonTriggerCRDelete(t *testing.T) { +func TestEnsureTektonTriggerCRNotExists(t *testing.T) { ctx, _, _ := ts.SetupFakeContextWithCancel(t) c := fake.Get(ctx) - err := TektonTriggerCRDelete(ctx, c.OperatorV1alpha1().TektonTriggers(), v1alpha1.TriggerResourceName) + + // when no instance exists, nil error is returned immediately + err := EnsureTektonTriggerCRNotExists(ctx, c.OperatorV1alpha1().TektonTriggers()) + util.AssertEqual(t, err, nil) + + // create an instance for testing other cases + tConfig := GetTektonConfig() + _, err = EnsureTektonTriggerExists(ctx, c.OperatorV1alpha1().TektonTriggers(), tConfig) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when an instance exists the first invoacation should make the delete API call and + // return RECONCILE_AGAI_ERROR. So that the deletion can be confirmed in a subsequent invocation + err = EnsureTektonTriggerCRNotExists(ctx, c.OperatorV1alpha1().TektonTriggers()) + util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) + + // when the instance is completely removed from a cluster, the function should return nil error + err = EnsureTektonTriggerCRNotExists(ctx, c.OperatorV1alpha1().TektonTriggers()) + util.AssertEqual(t, err, nil) +} + +func markTriggersReady(t *testing.T, ctx context.Context, c op.TektonTriggerInterface) { + t.Helper() + tr, err := c.Get(ctx, v1alpha1.TriggerResourceName, 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.TektonTriggerInterface) { + t.Helper() + // set necessary version labels to make upgrade check pass + trigger, err := c.Get(ctx, v1alpha1.TriggerResourceName, metav1.GetOptions{}) + util.AssertEqual(t, err, nil) + setDummyVersionLabel(trigger) + _, err = c.Update(ctx, trigger, metav1.UpdateOptions{}) + util.AssertEqual(t, err, nil) +} + +func setDummyVersionLabel(tr *v1alpha1.TektonTrigger) { + oprVersion := "v1.2.3" + os.Setenv(v1alpha1.VersionEnvKey, oprVersion) + + labels := tr.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[v1alpha1.ReleaseVersionKey] = oprVersion + tr.SetLabels(labels) +} diff --git a/test/e2e/common/00_tektonconfigdeployment_test.go b/test/e2e/common/00_tektonconfigdeployment_test.go index d97831a944..dd41d97b9a 100644 --- a/test/e2e/common/00_tektonconfigdeployment_test.go +++ b/test/e2e/common/00_tektonconfigdeployment_test.go @@ -35,6 +35,7 @@ import ( "github.com/tektoncd/operator/test/resources" "github.com/tektoncd/operator/test/utils" "github.com/tektoncd/pipeline/test/diff" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/ptr" ) @@ -155,8 +156,8 @@ func runFeatureTest(t *testing.T, clients *utils.Clients, tc *v1alpha1.TektonCon }) t.Run("change-spec-configuration-and-validate", func(t *testing.T) { - - tc, err := clients.Operator.TektonConfigs().Get(context.TODO(), v1alpha1.ConfigResourceName, metav1.GetOptions{}) + ctx := context.Background() + tc, err := clients.Operator.TektonConfigs().Get(ctx, v1alpha1.ConfigResourceName, metav1.GetOptions{}) if err != nil { t.Fatalf("failed to get tektonconfig: %v", err) } @@ -176,53 +177,76 @@ func runFeatureTest(t *testing.T, clients *utils.Clients, tc *v1alpha1.TektonCon // triggers config-defaults configMap tc.Spec.Trigger.OptionalTriggersProperties.DefaultServiceAccount = "foo" - tc, err = clients.Operator.TektonConfigs().Update(context.TODO(), tc, metav1.UpdateOptions{}) + tc, err = clients.Operator.TektonConfigs().Update(ctx, tc, metav1.UpdateOptions{}) if err != nil { t.Fatalf("failed to update tektonconfig: %v", err) } - // wait for a few seconds and it reconcile - time.Sleep(time.Second * 5) - - // Validate changes to Pipelines ConfigMaps + // Validate changes to Pipelines feature-flags ConfigMap + err = utils.WaitForCondition(ctx, func() (bool, error) { + featureFlags, err := clients.KubeClient.CoreV1(). + ConfigMaps(tc.Spec.TargetNamespace). + Get(context.TODO(), tektonpipeline.FeatureFlag, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + if featureFlags != nil && + featureFlags.Data["enable-custom-tasks"] != "true" || featureFlags.Data["enable-tekton-oci-bundles"] != "true" { + return false, nil + } + return true, nil + }) - featureFlags, err := clients.KubeClient.CoreV1().ConfigMaps(tc.Spec.TargetNamespace).Get(context.TODO(), tektonpipeline.FeatureFlag, metav1.GetOptions{}) if err != nil { - t.Fatalf("failed to get pipelines configMap: %s : %v", tektonpipeline.FeatureFlag, err) - } - - if featureFlags.Data["enable-custom-tasks"] != "true" || featureFlags.Data["enable-tekton-oci-bundles"] != "true" { t.Fatalf("failed to update changes to pipelines configMap: %s ", tektonpipeline.FeatureFlag) } - configDefaults, err := clients.KubeClient.CoreV1().ConfigMaps(tc.Spec.TargetNamespace).Get(context.TODO(), tektonpipeline.ConfigDefaults, metav1.GetOptions{}) - if err != nil { - t.Fatalf("failed to get pipelines configMap: %s : %v", tektonpipeline.ConfigDefaults, err) - } + // Validate changes to Pipelines config-defaults ConfigMap + err = utils.WaitForCondition(ctx, func() (bool, error) { + configDefaults, err := clients.KubeClient.CoreV1(). + ConfigMaps(tc.Spec.TargetNamespace). + Get(context.TODO(), tektonpipeline.ConfigDefaults, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + if configDefaults != nil && + configDefaults.Data["default-service-account"] != "foo" { + return false, nil + } + return true, nil + }) - if configDefaults.Data["default-service-account"] != "foo" { + if err != nil { t.Fatalf("failed to update changes to pipelines configMap: %s ", tektonpipeline.ConfigDefaults) } - // Validate changes to Triggers ConfigMaps + // Validate changes to Triggers feature-flag-triggers configMap + err = utils.WaitForCondition(ctx, func() (bool, error) { + featureFlags, err := clients.KubeClient.CoreV1(). + ConfigMaps(tc.Spec.TargetNamespace). + Get(context.TODO(), tektontrigger.FeatureFlag, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + if featureFlags != nil && + featureFlags.Data["enable-api-fields"] != v1alpha1.ApiFieldAlpha { + return false, nil + } + return true, nil + }) - featureFlags, err = clients.KubeClient.CoreV1().ConfigMaps(tc.Spec.TargetNamespace).Get(context.TODO(), tektontrigger.FeatureFlag, metav1.GetOptions{}) if err != nil { - t.Fatalf("failed to get triggers configMap: %s : %v", tektontrigger.FeatureFlag, err) - } - - if featureFlags.Data["enable-api-fields"] != v1alpha1.ApiFieldAlpha { t.Fatalf("failed to update changes to triggers configMap: %s", tektontrigger.FeatureFlag) } - - configDefaults, err = clients.KubeClient.CoreV1().ConfigMaps(tc.Spec.TargetNamespace).Get(context.TODO(), tektontrigger.ConfigDefaults, metav1.GetOptions{}) - if err != nil { - t.Fatalf("failed to get triggers configMap: %s : %v", tektontrigger.ConfigDefaults, err) - } - - if configDefaults.Data["default-service-account"] != "foo" { - t.Fatalf("failed to update changes to triggers configMap: %s :", tektontrigger.ConfigDefaults) - } }) t.Run("delete-component-and-recreate", func(t *testing.T) { diff --git a/test/utils/cleanup.go b/test/utils/cleanup.go index 29e586b9c9..0249a61180 100644 --- a/test/utils/cleanup.go +++ b/test/utils/cleanup.go @@ -22,7 +22,6 @@ import ( "os" "os/signal" - "github.com/tektoncd/operator/pkg/reconciler/common" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" @@ -195,7 +194,7 @@ func TearDownNamespace(clients *Clients, name string) { return } - err = waitForCondition(ctx, func() (bool, error) { + err = WaitForCondition(ctx, func() (bool, error) { _, err := clients.KubeClient.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) if err != nil { if errors.IsNotFound(err) { @@ -347,15 +346,15 @@ func waitUntilFullDeletion(ctx context.Context, cdcf crDeleteVerifier, ddcf depl } func ensureCustomResourceRemoval(ctx context.Context, verifier crDeleteVerifier) error { - return waitForCondition(ctx, wait.ConditionFunc(verifier)) + return WaitForCondition(ctx, wait.ConditionFunc(verifier)) } func ensureDeploymentsRemoval(ctx context.Context, verifier deploymentDeleteVerifier) error { - return waitForCondition(ctx, wait.ConditionFunc(verifier)) + return WaitForCondition(ctx, wait.ConditionFunc(verifier)) } -func waitForCondition(ctx context.Context, condition wait.ConditionFunc) error { - return wait.PollImmediate(common.Interval, common.Timeout, func() (done bool, err error) { +func WaitForCondition(ctx context.Context, condition wait.ConditionFunc) error { + return wait.PollImmediate(Interval, Timeout, func() (done bool, err error) { ok, err := condition() if err != nil { return false, err diff --git a/test/utils/config.go b/test/utils/config.go new file mode 100644 index 0000000000..191d6f859c --- /dev/null +++ b/test/utils/config.go @@ -0,0 +1,8 @@ +package utils + +import "time" + +var ( + Interval = 10 * time.Second + Timeout = 5 * time.Minute +)