Repository: vmware-tanzu/tanzu-framework (language: go)

Issue:
Enable kctrl command tree in package plugin by default
<!-- This is used to request new product features -->

**Describe the feature request**
Set default value of `features.package.kctrl-package-command-tree` to true. To use the old package plugin behaviour, we will have to set `features.package.kctrl-package-command-tree` to false manually. 

**Affected product area (please put an X in all that apply)**

- ( ) APIs
- ( ) Addons
- (x) CLI
- ( ) Docs
- ( ) IAM
- ( ) Installation
- (x) Plugin
- ( ) Security
- ( ) Test and Release
- ( ) User Experience
- ( ) Developer Experience

**Additional context**



Discussion / hints:

Fix patch:
diff --git a/cli/runtime/config/featureflags.go b/cli/runtime/config/featureflags.go
index e35bf1860c..3b42776c3a 100644
--- a/cli/runtime/config/featureflags.go
+++ b/cli/runtime/config/featureflags.go
@@ -85,7 +85,7 @@ var (
 		FeatureFlagClusterCustomNameservers:                   false,
 		FeatureFlagAwsInstanceTypesExcludeArm:                 true,
 		FeatureFlagTKRVersionV1Alpha3:                         false,
-		FeatureFlagPackagePluginKctrlCommandTree:              false,
+		FeatureFlagPackagePluginKctrlCommandTree:              true,
 	}
 )
 


Test patch:
diff --git a/cmd/cli/plugin/package/test/lib/package_plugin_kctrl.go b/cmd/cli/plugin/package/test/lib/package_plugin_kctrl.go
new file mode 100644
index 0000000000..c883b29be7
--- /dev/null
+++ b/cmd/cli/plugin/package/test/lib/package_plugin_kctrl.go
@@ -0,0 +1,485 @@
+package lib
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"k8s.io/apimachinery/pkg/util/wait"
+
+	clitest "github.com/vmware-tanzu/tanzu-framework/cli/runtime/test"
+	"github.com/vmware-tanzu/tanzu-framework/packageclients/pkg/packagedatamodel"
+)
+
+type packagePluginKctrl struct {
+	kubeconfigPath string
+	interval       time.Duration
+	timeout        time.Duration
+	outputFormat   string
+	logFile        string
+	verbose        int32
+}
+
+func NewPackagePluginKctrl(kubeconfigPath string,
+	interval time.Duration,
+	timeout time.Duration,
+	outputFormat string,
+	logFile string,
+	verbose int32) PackagePlugin {
+	return &packagePluginKctrl{
+		kubeconfigPath: kubeconfigPath,
+		interval:       interval,
+		timeout:        timeout,
+		outputFormat:   outputFormat,
+		logFile:        logFile,
+		verbose:        verbose,
+	}
+}
+
+func (p *packagePluginKctrl) addKubeconfig(cmd string) string {
+	if cmd != "" && p.kubeconfigPath != "" {
+		cmd += fmt.Sprintf(" --kubeconfig %s", p.kubeconfigPath)
+	}
+	return cmd
+}
+
+func (p *packagePluginKctrl) addOutputFormat(cmd string) string {
+	if cmd != "" && p.outputFormat != "" {
+		cmd += fmt.Sprintf(" --output %s", p.outputFormat)
+	}
+	return cmd
+}
+
+func (p *packagePluginKctrl) addLogFile(cmd string) string {
+	if cmd != "" && p.logFile != "" {
+		cmd += fmt.Sprintf(" --log-file %s", p.logFile)
+	}
+	return cmd
+}
+
+func (p *packagePluginKctrl) addVerbose(cmd string) string {
+	if cmd != "" && p.verbose != 0 {
+		cmd += fmt.Sprintf(" --verbose %d", p.verbose)
+	}
+	return cmd
+}
+
+func (p *packagePluginKctrl) addGlobalOptions(cmd string) string {
+	cmd = p.addLogFile(cmd)
+	cmd = p.addVerbose(cmd)
+	return cmd
+}
+
+func (p *packagePluginKctrl) AddRepository(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package repository add %s --url %s", o.RepositoryName, o.RepositoryURL)
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	if o.Wait {
+		cmd += fmt.Sprintf(" --wait=true")
+	}
+	if o.PollInterval != 0 {
+		cmd += fmt.Sprintf(" --wait-check-interval %s", o.PollInterval)
+	}
+	if o.PollTimeout != 0 {
+		cmd += fmt.Sprintf(" --wait-timeout %s", o.PollTimeout)
+	}
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) GetRepository(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package repository get %s", o.RepositoryName)
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	cmd = p.addOutputFormat(cmd)
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) UpdateRepository(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package repository update %s --url %s", o.RepositoryName, o.RepositoryURL)
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	if o.Wait {
+		cmd += fmt.Sprintf(" --wait=true")
+	}
+	if o.PollInterval != 0 {
+		cmd += fmt.Sprintf(" --wait-check-interval %s", o.PollInterval)
+	}
+	if o.PollTimeout != 0 {
+		cmd += fmt.Sprintf(" --wait-timeout %s", o.PollTimeout)
+	}
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) DeleteRepository(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package repository delete %s", o.RepositoryName)
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	if o.IsForceDelete {
+		cmd += fmt.Sprintf(" --force")
+	}
+	if o.Wait {
+		cmd += fmt.Sprintf(" --wait=true")
+	}
+	if o.PollInterval != 0 {
+		cmd += fmt.Sprintf(" --wait-check-interval %s", o.PollInterval)
+	}
+	if o.PollTimeout != 0 {
+		cmd += fmt.Sprintf(" --wait-timeout %s", o.PollTimeout)
+	}
+	if o.SkipPrompt {
+		cmd += fmt.Sprintf(" -y")
+	}
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) ListRepository(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package repository list")
+	if o.AllNamespaces {
+		cmd += fmt.Sprintf(" --all-namespaces")
+	}
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	cmd = p.addOutputFormat(cmd)
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) GetAvailablePackage(packageName string, o *packagedatamodel.PackageAvailableOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package available get %s", packageName)
+
+	if o.ValuesSchema {
+		cmd += fmt.Sprintf(" --values-schema")
+	}
+	if o.AllNamespaces {
+		cmd += fmt.Sprintf(" --all-namespaces")
+	}
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	cmd = p.addOutputFormat(cmd)
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) ListAvailablePackage(packageName string, o *packagedatamodel.PackageAvailableOptions) PackagePluginResult {
+	var (
+		cmd    string
+		result PackagePluginResult
+	)
+	if packageName == "" {
+		cmd = fmt.Sprintf("tanzu package available list")
+	} else {
+		cmd = fmt.Sprintf("tanzu package available list %s", packageName)
+	}
+	if o.AllNamespaces {
+		cmd += fmt.Sprintf(" --all-namespaces")
+	}
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	cmd = p.addOutputFormat(cmd)
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) CreateInstalledPackage(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package installed create %s --package %s", o.PkgInstallName, o.PackageName)
+	if o.Version != "" {
+		cmd += fmt.Sprintf(" --version %s", o.Version)
+	}
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	if o.ValuesFile != "" {
+		cmd += fmt.Sprintf(" --values-file %s", o.ValuesFile)
+	}
+	if o.ServiceAccountName != "" {
+		cmd += fmt.Sprintf(" --service-account-name %s", o.ServiceAccountName)
+	}
+	if o.Wait {
+		cmd += fmt.Sprintf(" --wait=true")
+	}
+	if o.PollInterval != 0 {
+		cmd += fmt.Sprintf(" --wait-check-interval %s", o.PollInterval)
+	}
+	if o.PollTimeout != 0 {
+		cmd += fmt.Sprintf(" --wait-timeout %s", o.PollTimeout)
+	}
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) GetInstalledPackage(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package installed get %s", o.PkgInstallName)
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	cmd = p.addOutputFormat(cmd)
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) UpdateInstalledPackage(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package installed update %s --package %s", o.PkgInstallName, o.PackageName)
+	if o.Version != "" {
+		cmd += fmt.Sprintf(" --version %s", o.Version)
+	}
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	if o.Install {
+		cmd += fmt.Sprintf(" --install")
+	}
+	if o.ValuesFile != "" {
+		cmd += fmt.Sprintf(" --values-file %s", o.ValuesFile)
+	}
+	if o.Wait {
+		cmd += fmt.Sprintf(" --wait=true")
+	}
+	if o.PollInterval != 0 {
+		cmd += fmt.Sprintf(" --wait-check-interval %s", o.PollInterval)
+	}
+	if o.PollTimeout != 0 {
+		cmd += fmt.Sprintf(" --wait-timeout %s", o.PollTimeout)
+	}
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) DeleteInstalledPackage(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package installed delete %s", o.PkgInstallName)
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	if o.PollInterval != 0 {
+		cmd += fmt.Sprintf(" --wait-check-interval %s", o.PollInterval)
+	}
+	if o.PollTimeout != 0 {
+		cmd += fmt.Sprintf(" --wait-timeout %s", o.PollTimeout)
+	}
+	if o.SkipPrompt {
+		cmd += fmt.Sprintf(" -y")
+	}
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) ListInstalledPackage(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	cmd := fmt.Sprintf("tanzu package installed list")
+	if o.Namespace != "" {
+		cmd += fmt.Sprintf(" --namespace %s", o.Namespace)
+	}
+	if o.AllNamespaces {
+		cmd += fmt.Sprintf(" -A")
+	}
+	cmd = p.addOutputFormat(cmd)
+	cmd = p.addKubeconfig(cmd)
+	cmd = p.addGlobalOptions(cmd)
+	result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd)
+	return result
+}
+
+func (p *packagePluginKctrl) AddOrUpdateRepository(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	getResult := p.GetRepository(&packagedatamodel.RepositoryOptions{
+		RepositoryName: o.RepositoryName,
+		Namespace:      o.Namespace,
+	})
+	if getResult.Error != nil {
+		return p.AddRepository(o)
+	} else {
+		if !strings.Contains(getResult.Stdout.String(), o.RepositoryURL) {
+			return p.UpdateRepository(o)
+		}
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckRepositoryAvailable(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	if err := wait.PollImmediate(p.interval, p.timeout, func() (done bool, err error) {
+		result := p.GetRepository(&packagedatamodel.RepositoryOptions{
+			RepositoryName: o.RepositoryName,
+			Namespace:      o.Namespace,
+		})
+		if result.Error != nil {
+			return false, result.Error
+		}
+		if result.Stdout != nil && strings.Contains(result.Stdout.String(), "Reconcile succeeded") {
+			return true, nil
+		}
+		return false, nil
+	}); err != nil {
+		if result.Error == nil {
+			result.Error = err
+		}
+		return result
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckAndDeleteRepository(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	getResult := p.GetRepository(&packagedatamodel.RepositoryOptions{
+		RepositoryName: o.RepositoryName,
+		Namespace:      o.Namespace,
+	})
+	if getResult.Error == nil {
+		return p.DeleteRepository(o)
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckRepositoryDeleted(o *packagedatamodel.RepositoryOptions) PackagePluginResult {
+	var result PackagePluginResult
+	if err := wait.PollImmediate(p.interval, p.timeout, func() (done bool, err error) {
+		result := p.GetRepository(&packagedatamodel.RepositoryOptions{
+			RepositoryName: o.RepositoryName,
+			Namespace:      o.Namespace,
+		})
+		if result.Stderr != nil && strings.Contains(result.Stderr.String(), "does not exist") {
+			if result.Error != nil {
+				// Setting result error to nil since there will be error on get after repository is deleted
+				result.Error = nil
+			}
+			return true, nil
+		}
+		return false, result.Error
+	}); err != nil {
+		if result.Error == nil {
+			result.Error = err
+		}
+		return result
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckPackageAvailable(packageName string, o *packagedatamodel.PackageAvailableOptions) PackagePluginResult {
+	var result PackagePluginResult
+	if err := wait.PollImmediate(p.interval, p.timeout, func() (done bool, err error) {
+		result = p.GetAvailablePackage(packageName, o)
+		if result.Error == nil {
+			return true, nil
+		}
+		return false, nil
+	}); err != nil {
+		if result.Error == nil {
+			result.Error = err
+		}
+		return result
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckAndInstallPackage(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	getResult := p.GetInstalledPackage(&packagedatamodel.PackageOptions{
+		PkgInstallName: o.PkgInstallName,
+		Namespace:      o.Namespace,
+	})
+	if getResult.Error != nil {
+		return p.CreateInstalledPackage(o)
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckPackageInstalled(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	if err := wait.PollImmediate(p.interval, p.timeout, func() (done bool, err error) {
+		result = p.GetInstalledPackage(&packagedatamodel.PackageOptions{
+			PkgInstallName: o.PkgInstallName,
+			Namespace:      o.Namespace,
+		})
+		if result.Error != nil {
+			return false, result.Error
+		}
+		if result.Stdout != nil && strings.Contains(result.Stdout.String(), "Reconcile succeeded") {
+			return true, nil
+		}
+		return false, nil
+	}); err != nil {
+		if result.Error == nil {
+			result.Error = err
+		}
+		return result
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckAndUninstallPackage(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	getResult := p.GetInstalledPackage(&packagedatamodel.PackageOptions{
+		PkgInstallName: o.PkgInstallName,
+		Namespace:      o.Namespace,
+	})
+	if getResult.Error == nil {
+		return p.DeleteInstalledPackage(o)
+	}
+	return result
+}
+
+func (p *packagePluginKctrl) CheckPackageDeleted(o *packagedatamodel.PackageOptions) PackagePluginResult {
+	var result PackagePluginResult
+	if err := wait.PollImmediate(p.interval, p.timeout, func() (done bool, err error) {
+		result = p.GetInstalledPackage(&packagedatamodel.PackageOptions{
+			PkgInstallName: o.PkgInstallName,
+			Namespace:      o.Namespace,
+		})
+		if result.Stderr != nil && strings.Contains(result.Stderr.String(), "does not exist") {
+			if result.Error != nil {
+				// Setting result error to nil since there will be error on get after package is deleted
+				result.Error = nil
+			}
+			return true, nil
+		}
+		return false, result.Error
+	}); err != nil {
+		if result.Error == nil {
+			result.Error = err
+		}
+		return result
+	}
+	return result
+}
diff --git a/cmd/cli/plugin/package/test/package_plugin_integration_test.go b/cmd/cli/plugin/package/test/package_plugin_integration_test.go
index 9d48994e60..8244209a9e 100644
--- a/cmd/cli/plugin/package/test/package_plugin_integration_test.go
+++ b/cmd/cli/plugin/package/test/package_plugin_integration_test.go
@@ -66,7 +66,7 @@ type registrySecretOutput struct {
 	Namespace string `json:"namespace"`
 }
 
-type repositoryOutput struct {
+type repositoryOutputKctrlDisabled struct {
 	Name       string `json:"name"`
 	Repository string `json:"repository"`
 	Tag        string `json:"tag"`
@@ -74,6 +74,13 @@ type repositoryOutput struct {
 	Namespace  string `json:"namespace"`
 }
 
+type repositoryOutput struct {
+	Name      string `json:"name"`
+	Source    string `json:"source"`
+	Status    string `json:"status"`
+	Namespace string `json:"namespace"`
+}
+
 type packageInstalledOutput struct {
 	Name           string `json:"name"`
 	PackageName    string `json:"package-name"`
@@ -86,47 +93,51 @@ type packageAvailableOutput struct {
 }
 
 var (
-	config                      = &PackagePluginConfig{}
-	configPath                  string
-	tkgCfgDir                   string
-	err                         error
-	packagePlugin               packagelib.PackagePlugin
-	secretPlugin                secretlib.SecretPlugin
-	result                      packagelib.PackagePluginResult
-	resultImgPullSecret         secretlib.SecretPluginResult
-	clusterCreationTimeout      = 30 * time.Minute
-	pollInterval                = 20 * time.Second
-	pollTimeout                 = 10 * time.Minute
-	testImgPullSecretName       = "test-secret"
-	testNamespace               = "test-ns"
-	privateTestRegistry         = "registry-svc.registry.svc.cluster.local:443"
-	privateTestRegistryUsername = "testuser"
-	privateTestRegistryPassword = "testpassword"
-	testRepoName                = "carvel-test"
-	testRepoURL                 = "projects-stg.registry.vmware.com/tkg/test-packages/test-repo:v1.0.0"
-	testRepoURLNoTag            = "projects-stg.registry.vmware.com/tkg/test-packages/test-repo"
-	testRepoURLPrivate          = "registry-svc.registry.svc.cluster.local:443/secret-test/test-repo@sha256:e07483e2140fa427d9875aee9055d72efc49a732f3a3fb2c9651d9f39159315a"
-	testRepoURLPrivateNoTag     = "registry-svc.registry.svc.cluster.local:443/secret-test/test-repo"
-	testRepoOriginalTag         = "v1.0.0"
-	testRepoLatestTag           = "v1.1.0"
-	testRepoPrivateTag          = "sha256:e07483e2140fa427d9875aee9055d72efc49a732f3a3fb2c9651d9f39159315a"
-	testPkgInstallName          = "test-pkg"
-	testPkgName                 = "pkg.test.carvel.dev"
-	testPkgVersion              = "2.0.0"
-	testPkgVersionUpdate        = "3.0.0-rc.1"
-	imgPullSecretOptions        packagedatamodel.RegistrySecretOptions
-	pkgAvailableOptions         packagedatamodel.PackageAvailableOptions
-	pkgOptions                  packagedatamodel.PackageOptions
-	repoOptions                 packagedatamodel.RepositoryOptions
-	expectedPkgOutput           packageInstalledOutput
-	expectedPkgOutputUpdate     packageInstalledOutput
-	expectedRepoOutput          repositoryOutput
-	expectedRepoOutputLatestTag repositoryOutput
-	expectedRepoOutputPrivate   repositoryOutput
-	imgPullSecretOutput         []registrySecretOutput
-	pkgOutput                   []packageInstalledOutput
-	pkgAvailableOutput          []packageAvailableOutput
-	repoOutput                  []repositoryOutput
+	config                         = &PackagePluginConfig{}
+	configPath                     string
+	tkgCfgDir                      string
+	err                            error
+	packagePlugin                  packagelib.PackagePlugin
+	secretPlugin                   secretlib.SecretPlugin
+	result                         packagelib.PackagePluginResult
+	resultImgPullSecret            secretlib.SecretPluginResult
+	clusterCreationTimeout         = 30 * time.Minute
+	pollInterval                   = 20 * time.Second
+	pollTimeout                    = 10 * time.Minute
+	testImgPullSecretName          = "test-secret"
+	testNamespace                  = "test-ns"
+	privateTestRegistry            = "registry-svc.registry.svc.cluster.local:443"
+	privateTestRegistryUsername    = "testuser"
+	privateTestRegistryPassword    = "testpassword"
+	testRepoName                   = "carvel-test"
+	testRepoURL                    = "projects-stg.registry.vmware.com/tkg/test-packages/test-repo:v1.0.0"
+	testRepoURLNoTag               = "projects-stg.registry.vmware.com/tkg/test-packages/test-repo"
+	testRepoURLPrivate             = "registry-svc.registry.svc.cluster.local:443/secret-test/test-repo@sha256:e07483e2140fa427d9875aee9055d72efc49a732f3a3fb2c9651d9f39159315a"
+	testRepoURLPrivateNoTag        = "registry-svc.registry.svc.cluster.local:443/secret-test/test-repo"
+	testRepoOriginalTag            = "v1.0.0"
+	testRepoLatestTag              = "v1.1.0"
+	testRepoPrivateTag             = "sha256:e07483e2140fa427d9875aee9055d72efc49a732f3a3fb2c9651d9f39159315a"
+	testPkgInstallName             = "test-pkg"
+	testPkgName                    = "pkg.test.carvel.dev"
+	testPkgVersion                 = "2.0.0"
+	testPkgVersionUpdate           = "3.0.0-rc.1"
+	imgPullSecretOptions           packagedatamodel.RegistrySecretOptions
+	pkgAvailableOptions            packagedatamodel.PackageAvailableOptions
+	pkgOptions                     packagedatamodel.PackageOptions
+	repoOptions                    packagedatamodel.RepositoryOptions
+	expectedPkgOutput              packageInstalledOutput
+	expectedPkgOutputUpdate        packageInstalledOutput
+	expectedRepoOutput             repositoryOutput
+	expectedRepoOutputNoTag        repositoryOutput
+	expectedRepoOutputPrivate      repositoryOutput
+	expectedRepoOutputOld          repositoryOutputKctrlDisabled
+	expectedRepoOutputLatestTagOld repositoryOutputKctrlDisabled
+	expectedRepoOutputPrivateOld   repositoryOutputKctrlDisabled
+	imgPullSecretOutput            []registrySecretOutput
+	pkgOutput                      []packageInstalledOutput
+	pkgAvailableOutput             []packageAvailableOutput
+	repoOutput                     []repositoryOutput
+	repoOutputOld                  []repositoryOutputKctrlDisabled
 )
 
 var _ = Describe("Package plugin integration test", func() {
@@ -316,6 +327,27 @@ var _ = Describe("Package plugin integration test", func() {
 		}
 
 		expectedRepoOutput = repositoryOutput{
+			Name:      config.RepositoryName,
+			Source:    fmt.Sprintf("(imgpkg) %s:%s", config.RepositoryURLNoTag, config.RepositoryOriginalTag),
+			Status:    "Reconcile succeeded",
+			Namespace: config.Namespace,
+		}
+
+		expectedRepoOutputNoTag = repositoryOutput{
+			Name:      config.RepositoryName,
+			Source:    fmt.Sprintf("(imgpkg) %s", config.RepositoryURLNoTag),
+			Status:    "Reconcile succeeded",
+			Namespace: config.Namespace,
+		}
+
+		expectedRepoOutputPrivate = repositoryOutput{
+			Name:      config.RepositoryName,
+			Source:    fmt.Sprintf("(imgpkg) %s@%s", config.RepositoryURLPrivateNoTag, config.RepositoryPrivateTag),
+			Status:    "Reconcile succeeded",
+			Namespace: config.Namespace,
+		}
+
+		expectedRepoOutputOld = repositoryOutputKctrlDisabled{
 			Name:       config.RepositoryName,
 			Repository: config.RepositoryURLNoTag,
 			Tag:        config.RepositoryOriginalTag,
@@ -323,7 +355,7 @@ var _ = Describe("Package plugin integration test", func() {
 			Namespace:  config.Namespace,
 		}
 
-		expectedRepoOutputLatestTag = repositoryOutput{
+		expectedRepoOutputLatestTagOld = repositoryOutputKctrlDisabled{
 			Name:       config.RepositoryName,
 			Repository: config.RepositoryURLNoTag,
 			Tag:        "(>0.0.0)",
@@ -331,7 +363,7 @@ var _ = Describe("Package plugin integration test", func() {
 			Namespace:  config.Namespace,
 		}
 
-		expectedRepoOutputPrivate = repositoryOutput{
+		expectedRepoOutputPrivateOld = repositoryOutputKctrlDisabled{
 			Name:       config.RepositoryName,
 			Repository: config.RepositoryURLPrivateNoTag,
 			Tag:        config.RepositoryPrivateTag,
@@ -356,12 +388,13 @@ var _ = Describe("Package plugin integration test", func() {
 
 	Context("testing package plugin on management cluster", func() {
 		BeforeEach(func() {
-			packagePlugin = packagelib.NewPackagePlugin(config.KubeConfigPathMC, pollInterval, pollTimeout, "json", "", 0)
+			packagePlugin = packagelib.NewPackagePluginKctrl(config.KubeConfigPathMC, pollInterval, pollTimeout, "json", "", 0)
 			secretPlugin = secretlib.NewSecretPlugin(config.KubeConfigPathMC, pollInterval, pollTimeout, "json", 0)
 		})
 		It("should pass all checks on management cluster", func() {
 			cleanup()
 			setUpPrivateRegistry(config.KubeConfigPathMC, config.ClusterNameMC)
+			createTestNamespace(config.KubeConfigPathMC, config.ClusterNameMC, config.Namespace)
 			testHelper()
 			log.Info("Successfully finished package plugin integration tests on management cluster")
 		})
@@ -369,7 +402,7 @@ var _ = Describe("Package plugin integration test", func() {
 
 	Context("testing package plugin on workload cluster", func() {
 		BeforeEach(func() {
-			packagePlugin = packagelib.NewPackagePlugin(config.KubeConfigPathWLC, pollInterval, pollTimeout, "json", "", 0)
+			packagePlugin = packagelib.NewPackagePluginKctrl(config.KubeConfigPathWLC, pollInterval, pollTimeout, "json", "", 0)
 			secretPlugin = secretlib.NewSecretPlugin(config.KubeConfigPathWLC, pollInterval, pollTimeout, "json", 0)
 		})
 		AfterEach(func() {
@@ -381,10 +414,45 @@ var _ = Describe("Package plugin integration test", func() {
 			paused := true
 			pauseKappControllerPackage(config.ClusterNameWLC, config.ClusterNamespaceWLC, config.KubeConfigPathMC, config.ClusterNameMC, paused)
 			setUpPrivateRegistry(config.KubeConfigPathWLC, config.ClusterNameWLC)
+			createTestNamespace(config.KubeConfigPathWLC, config.ClusterNameWLC, config.Namespace)
 			testHelper()
 			log.Info("Successfully finished package plugin integration tests on workload cluster")
 		})
 	})
+
+	Context("testing package plugin on management cluster with kctrl disabled", func() {
+		BeforeEach(func() {
+			packagePlugin = packagelib.NewPackagePlugin(config.KubeConfigPathMC, pollInterval, pollTimeout, "json", "", 0)
+			secretPlugin = secretlib.NewSecretPlugin(config.KubeConfigPathMC, pollInterval, pollTimeout, "json", 0)
+		})
+		It("should pass all checks on management cluster", func() {
+			disableKctrlCommandTree()
+			cleanup()
+			setUpPrivateRegistry(config.KubeConfigPathMC, config.ClusterNameMC)
+			testHelperKctrlDisabled()
+			log.Info("Successfully finished package plugin integration tests on management cluster")
+		})
+	})
+
+	Context("testing package plugin on workload cluster with kctrl disabled", func() {
+		BeforeEach(func() {
+			packagePlugin = packagelib.NewPackagePlugin(config.KubeConfigPathWLC, pollInterval, pollTimeout, "json", "", 0)
+			secretPlugin = secretlib.NewSecretPlugin(config.KubeConfigPathWLC, pollInterval, pollTimeout, "json", 0)
+		})
+		AfterEach(func() {
+			paused := false
+			pauseKappControllerPackage(config.ClusterNameWLC, config.ClusterNamespaceWLC, config.KubeConfigPathMC, config.ClusterNameMC, paused)
+		})
+		It("should pass all checks on workload cluster", func() {
+			disableKctrlCommandTree()
+			cleanup()
+			paused := true
+			pauseKappControllerPackage(config.ClusterNameWLC, config.ClusterNamespaceWLC, config.KubeConfigPathMC, config.ClusterNameMC, paused)
+			setUpPrivateRegistry(config.KubeConfigPathWLC, config.ClusterNameWLC)
+			testHelperKctrlDisabled()
+			log.Info("Successfully finished package plugin integration tests on workload cluster")
+		})
+	})
 })
 
 func cleanup() {
@@ -393,7 +461,7 @@ func cleanup() {
 	Expect(result.Error).ToNot(HaveOccurred())
 
 	By("cleanup previous package repository installation")
-	result = packagePlugin.DeleteRepository(&repoOptions)
+	result = packagePlugin.CheckAndDeleteRepository(&repoOptions)
 	Expect(result.Error).ToNot(HaveOccurred())
 
 	By("cleanup previous secret installation")
@@ -525,6 +593,19 @@ func setUpPrivateRegistry(kubeconfigPath, clusterName string) {
 	Expect(err).ToNot(HaveOccurred())
 }
 
+func createTestNamespace(kubeconfigPath, clusterName, testNamespace string) {
+	By("create test namespace in cluster")
+	kubeCtx := clusterName + "-admin@" + clusterName
+
+	command := exec.NewCommand(
+		exec.WithCommand("kubectl"),
+		exec.WithArgs("create", "ns", testNamespace, "--context", kubeCtx, "--kubeconfig", kubeconfigPath),
+		exec.WithStdout(GinkgoWriter),
+	)
+	err = command.RunAndRedirectOutput(context.Background())
+	Expect(err).ToNot(HaveOccurred())
+}
+
 func pauseKappControllerPackage(clusterName, clusterNamespace, mgmtClusterKubeconfigPath, mgmtClusterName string, pause bool) {
 	By("pausing kapp controller app")
 	mgmtClusterKubeCtx := mgmtClusterName + "-admin@" + mgmtClusterName
@@ -538,12 +619,22 @@ func pauseKappControllerPackage(clusterName, clusterNamespace, mgmtClusterKubeco
 	Expect(err).ToNot(HaveOccurred())
 }
 
+func disableKctrlCommandTree() {
+	By("disable kctrl command tree")
+	command := exec.NewCommand(
+		exec.WithCommand("tanzu"),
+		exec.WithArgs("config", "set", "features.package.kctrl-package-command-tree", "false"),
+		exec.WithStdout(GinkgoWriter),
+	)
+	err = command.RunAndRedirectOutput(context.Background())
+	Expect(err).ToNot(HaveOccurred())
+}
+
 func testHelper() {
-	By("trying to update package repository with a private URL")
+	By("trying to create package repository with a private URL")
 	repoOptions.RepositoryURL = config.RepositoryURLPrivate
-	repoOptions.CreateRepository = true
 	repoOptions.PollTimeout = 30 * time.Second
-	result = packagePlugin.UpdateRepository(&repoOptions)
+	result = packagePlugin.AddRepository(&repoOptions)
 	Expect(result.Error).To(HaveOccurred())
 
 	By("add registry secret")
@@ -568,7 +659,6 @@ func testHelper() {
 
 	By("wait for the private package repository reconciliation")
 	repoOptions.RepositoryURL = config.RepositoryURLPrivate
-	repoOptions.CreateRepository = true
 	repoOptions.PollInterval = pollInterval
 	repoOptions.PollTimeout = pollTimeout
 	result = packagePlugin.CheckRepositoryAvailable(&repoOptions)
@@ -590,6 +680,175 @@ func testHelper() {
 	Expect(len(repoOutput)).To(BeNumerically("==", 1))
 	Expect(repoOutput[0]).To(Equal(expectedRepoOutputPrivate))
 
+	By("update package repository with a new URL without tag")
+	repoOptions.RepositoryURL = config.RepositoryURLNoTag
+	repoOptions.PollInterval = pollInterval
+	repoOptions.PollTimeout = pollTimeout
+	result = packagePlugin.UpdateRepository(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+
+	By("get package repository")
+	repoOutput = []repositoryOutput{{Namespace: testNamespace}}
+	result = packagePlugin.GetRepository(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(repoOutput)).To(BeNumerically("==", 1))
+	Expect(repoOutput[0]).To(Equal(expectedRepoOutputNoTag))
+
+	By("update package repository with a new URL")
+	repoOptions.RepositoryURL = config.RepositoryURL
+	result = packagePlugin.UpdateRepository(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+
+	By("get package repository")
+	repoOutput = []repositoryOutput{{Namespace: testNamespace}}
+	result = packagePlugin.GetRepository(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(repoOutput)).To(BeNumerically("==", 1))
+	Expect(repoOutput[0]).To(Equal(expectedRepoOutput))
+
+	By("list package available without packagename argument")
+	pkgAvailableOptions.AllNamespaces = true
+	result = packagePlugin.ListAvailablePackage("", &pkgAvailableOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &pkgAvailableOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(pkgAvailableOutput)).To(BeNumerically(">=", 1))
+	Expect(pkgAvailableOutput).To(ContainElement(packageAvailableOutput{Name: config.PackageName}))
+
+	By("list package available with packagename argument")
+	pkgAvailableOptions.AllNamespaces = true
+	result = packagePlugin.ListAvailablePackage(config.PackageName, &pkgAvailableOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &pkgAvailableOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(pkgAvailableOutput).To(ContainElement(packageAvailableOutput{Name: config.PackageName}))
+
+	By("get package available packagename format")
+	pkgAvailableOptions.AllNamespaces = false
+	result = packagePlugin.GetAvailablePackage(config.PackageName, &pkgAvailableOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &pkgAvailableOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(pkgAvailableOutput)).To(BeNumerically("==", 1))
+	Expect(pkgAvailableOutput[0].Name).To(Equal(config.PackageName))
+
+	By("get package available packagename/packageversion format")
+	result = packagePlugin.GetAvailablePackage(config.PackageName+"/"+config.PackageVersion, &pkgAvailableOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &pkgAvailableOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(pkgAvailableOutput)).To(BeNumerically("==", 1))
+	Expect(pkgAvailableOutput[0].Name).To(Equal(config.PackageName))
+
+	By("create package install")
+	pkgOptions.PollInterval = pollInterval
+	pkgOptions.PollTimeout = pollTimeout
+	pkgOptions.Version = config.PackageVersion
+	if config.WithValueFile {
+		pkgOptions.ValuesFile = "config/values.yaml"
+	}
+	result = packagePlugin.CreateInstalledPackage(&pkgOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+
+	By("list package install")
+	pkgOptions.AllNamespaces = true
+	result = packagePlugin.ListInstalledPackage(&pkgOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &pkgOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(pkgOutput)).To(BeNumerically(">=", 1))
+	Expect(pkgOutput).To(ContainElement(expectedPkgOutput))
+
+	By("update package install")
+	pkgOptions.Version = config.PackageVersionUpdate
+	if config.WithValueFile {
+		pkgOptions.ValuesFile = "config/values_update.yaml"
+	}
+	result = packagePlugin.UpdateInstalledPackage(&pkgOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+
+	By("get package install")
+	result = packagePlugin.GetInstalledPackage(&pkgOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &pkgOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(pkgOutput)).To(BeNumerically("==", 1))
+	Expect(pkgOutput[0]).To(Equal(expectedPkgOutputUpdate))
+
+	By("delete package install")
+	result = packagePlugin.DeleteInstalledPackage(&pkgOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+
+	By("delete package repository")
+	repoOptions.PollInterval = pollInterval
+	result = packagePlugin.DeleteRepository(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+
+	By("check package repository deletion")
+	result = packagePlugin.GetRepository(&repoOptions)
+	Expect(result.Error).To(HaveOccurred())
+
+	By("delete registry secret")
+	resultImgPullSecret = secretPlugin.DeleteRegistrySecret(&imgPullSecretOptions)
+	Expect(resultImgPullSecret.Error).ToNot(HaveOccurred())
+}
+
+func testHelperKctrlDisabled() {
+	By("trying to update package repository with a private URL")
+	repoOptions.RepositoryURL = config.RepositoryURLPrivate
+	repoOptions.CreateRepository = true
+	repoOptions.PollTimeout = 30 * time.Second
+	result = packagePlugin.UpdateRepository(&repoOptions)
+	Expect(result.Error).To(HaveOccurred())
+
+	By("add registry secret")
+	imgPullSecretOptions.Username = privateTestRegistryUsername
+	imgPullSecretOptions.PasswordInput = privateTestRegistryPassword
+	imgPullSecretOptions.Server = privateTestRegistry
+	resultImgPullSecret = secretPlugin.AddRegistrySecret(&imgPullSecretOptions)
+	Expect(resultImgPullSecret.Error).ToNot(HaveOccurred())
+
+	By("update registry secret to export the secret from default namespace to all namespaces")
+	t := true
+	imgPullSecretOptions.Export = packagedatamodel.TypeBoolPtr{ExportToAllNamespaces: &t}
+	resultImgPullSecret = secretPlugin.UpdateRegistrySecret(&imgPullSecretOptions)
+	Expect(resultImgPullSecret.Error).ToNot(HaveOccurred())
+
+	By("list registry secret")
+	resultImgPullSecret = secretPlugin.ListRegistrySecret(&imgPullSecretOptions)
+	Expect(resultImgPullSecret.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(resultImgPullSecret.Stdout.Bytes(), &imgPullSecretOutput)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(imgPullSecretOutput)).To(BeNumerically(">=", 1))
+
+	By("wait for the private package repository reconciliation")
+	repoOptions.RepositoryURL = config.RepositoryURLPrivate
+	repoOptions.CreateRepository = true
+	repoOptions.PollInterval = pollInterval
+	repoOptions.PollTimeout = pollTimeout
+	result = packagePlugin.CheckRepositoryAvailable(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+
+	By("list package repository")
+	repoOptions.AllNamespaces = true
+	result = packagePlugin.ListRepository(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutputOld)
+	Expect(err).ToNot(HaveOccurred())
+
+	By("get package repository")
+	repoOutputOld = []repositoryOutputKctrlDisabled{{Namespace: testNamespace}}
+	result = packagePlugin.GetRepository(&repoOptions)
+	Expect(result.Error).ToNot(HaveOccurred())
+	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutputOld)
+	Expect(err).ToNot(HaveOccurred())
+	Expect(len(repoOutputOld)).To(BeNumerically("==", 1))
+	Expect(repoOutputOld[0]).To(Equal(expectedRepoOutputPrivateOld))
+
 	By("update package repository with a new URL without tag")
 	repoOptions.RepositoryURL = config.RepositoryURLNoTag
 	repoOptions.CreateRepository = true
@@ -604,13 +863,13 @@ func testHelper() {
 	Expect(result.Error).ToNot(HaveOccurred())
 
 	By("get package repository")
-	repoOutput = []repositoryOutput{{Namespace: testNamespace}}
+	repoOutputOld = []repositoryOutputKctrlDisabled{{Namespace: testNamespace}}
 	result = packagePlugin.GetRepository(&repoOptions)
 	Expect(result.Error).ToNot(HaveOccurred())
-	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutput)
+	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutputOld)
 	Expect(err).ToNot(HaveOccurred())
-	Expect(len(repoOutput)).To(BeNumerically("==", 1))
-	Expect(repoOutput[0]).To(Equal(expectedRepoOutputLatestTag))
+	Expect(len(repoOutputOld)).To(BeNumerically("==", 1))
+	Expect(repoOutputOld[0]).To(Equal(expectedRepoOutputLatestTagOld))
 
 	By("update package repository with a new URL")
 	repoOptions.RepositoryURL = config.RepositoryURL
@@ -622,13 +881,13 @@ func testHelper() {
 	Expect(result.Error).ToNot(HaveOccurred())
 
 	By("get package repository")
-	repoOutput = []repositoryOutput{{Namespace: testNamespace}}
+	repoOutputOld = []repositoryOutputKctrlDisabled{{Namespace: testNamespace}}
 	result = packagePlugin.GetRepository(&repoOptions)
 	Expect(result.Error).ToNot(HaveOccurred())
-	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutput)
+	err = json.Unmarshal(result.Stdout.Bytes(), &repoOutputOld)
 	Expect(err).ToNot(HaveOccurred())
-	Expect(len(repoOutput)).To(BeNumerically("==", 1))
-	Expect(repoOutput[0]).To(Equal(expectedRepoOutput))
+	Expect(len(repoOutputOld)).To(BeNumerically("==", 1))
+	Expect(repoOutputOld[0]).To(Equal(expectedRepoOutputOld))
 
 	By("list package available without packagename argument")
 	pkgAvailableOptions.AllNamespaces = true