diff --git a/docs/plugindev/README.md b/docs/plugindev/README.md index 6a8a81c0c..b5d68baaf 100644 --- a/docs/plugindev/README.md +++ b/docs/plugindev/README.md @@ -265,12 +265,34 @@ as it would introduce a security risk. ### Testing the plugins +#### Manual testing + +As a plugin developer, you will want to test your plugin manually at some point. +During the development cycle, you can easily build and install the latest version of your plugin +as described in the section on [building a plugin](#building-a-plugin). + +If you are using "contexts" while doing testing of your plugin and that your plugin is context-scoped +you may find that your new plugin version is being overwritten with an older version by the CLI. +When a plugin is context-scoped a context may recommend a specific version of that plugin for the CLI +to use. In such a case, if you want to test your new version and prevent the CLI from overwriting it +with the version recommended by the context you can enable the `TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS` +variable by doing: + +```sh +tanzu config set env.TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS true +``` + +This variable will instruct the CLI that any installation done with `tanzu plugin install ` +should take precedence over any installation coming from a context. + +#### Automated tests using the test plugin + Plugin tests can be run by installing the admin `test` plugin. Currently, we only support testing plugins built locally. **Note:** The `test` admin functionality has been deprecated and no future enhancements are planned for this plugin. -Steps to test plugin :- +Steps to test a plugin: 1. Bootstrap a new plugin 2. Build a plugin binary diff --git a/pkg/constants/env_variables.go b/pkg/constants/env_variables.go index 26ca2a910..c832d2882 100644 --- a/pkg/constants/env_variables.go +++ b/pkg/constants/env_variables.go @@ -12,6 +12,7 @@ const ( AllowedRegistries = "ALLOWED_REGISTRY" ConfigVariableAdditionalDiscoveryForTesting = "TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY" ConfigVariableIncludeDeactivatedPluginsForTesting = "TANZU_CLI_INCLUDE_DEACTIVATED_PLUGINS_TEST_ONLY" + ConfigVariableStandaloneOverContextPlugins = "TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS" // PluginDiscoveryImageSignatureVerificationSkipList is a comma separated list of discovery image urls PluginDiscoveryImageSignatureVerificationSkipList = "TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_VERIFICATION_SKIP_LIST" PublicKeyPathForPluginDiscoveryImageSignature = "TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_PUBLIC_KEY_PATH" diff --git a/pkg/pluginsupplier/plugin_supplier.go b/pkg/pluginsupplier/plugin_supplier.go index 6e3d57854..7b99bbda9 100644 --- a/pkg/pluginsupplier/plugin_supplier.go +++ b/pkg/pluginsupplier/plugin_supplier.go @@ -5,8 +5,12 @@ package pluginsupplier import ( + "os" + "strconv" + "github.com/vmware-tanzu/tanzu-cli/pkg/catalog" "github.com/vmware-tanzu/tanzu-cli/pkg/cli" + "github.com/vmware-tanzu/tanzu-cli/pkg/constants" configlib "github.com/vmware-tanzu/tanzu-plugin-runtime/config" ) @@ -27,62 +31,90 @@ func GetInstalledPlugins() ([]cli.PluginInfo, error) { // GetInstalledStandalonePlugins returns the installed standalone plugins. func GetInstalledStandalonePlugins() ([]cli.PluginInfo, error) { - standAloneCatalog, err := catalog.NewContextCatalog("") + standalonePlugins, _, err := getInstalledStandaloneAndServerPlugins() if err != nil { return nil, err } - plugins := standAloneCatalog.List() + return standalonePlugins, nil +} - // Any server plugin installed takes precedence over the same plugin - // installed as standalone. We therefore remove those standalone - // plugins from the list. - plugins, err = removeInstalledServerPlugins(plugins) +// GetInstalledServerPlugins returns the installed server plugins. +func GetInstalledServerPlugins() ([]cli.PluginInfo, error) { + _, serverPlugins, err := getInstalledStandaloneAndServerPlugins() if err != nil { return nil, err } - return plugins, nil + return serverPlugins, nil } -// GetInstalledServerPlugins returns the installed server plugins. -func GetInstalledServerPlugins() ([]cli.PluginInfo, error) { - serverNames, err := configlib.GetAllCurrentContextsList() +func getInstalledStandaloneAndServerPlugins() (standalonePlugins, serverPlugins []cli.PluginInfo, err error) { + // Get all the standalone plugins found in the catalog + standAloneCatalog, err := catalog.NewContextCatalog("") if err != nil { - return nil, err + return nil, nil, err } + standalonePlugins = standAloneCatalog.List() - var serverPlugins []cli.PluginInfo + // Get all the server plugins found in the catalog + serverNames, err := configlib.GetAllCurrentContextsList() + if err != nil { + return nil, nil, err + } for _, serverName := range serverNames { if serverName != "" { serverCatalog, err := catalog.NewContextCatalog(serverName) if err != nil { - return nil, err + return nil, nil, err } serverPlugins = append(serverPlugins, serverCatalog.List()...) } } - return serverPlugins, nil + // If the same plugin is present both as standalone and + // as a server plugin we need to select which one to use + // based on the TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS variable + standalonePlugins, serverPlugins = filterIdenticalStandaloneAndServerPlugins(standalonePlugins, serverPlugins) + return standalonePlugins, serverPlugins, nil } -// Remove any installed standalone plugin if it is also installed as a server plugin. -func removeInstalledServerPlugins(standalone []cli.PluginInfo) ([]cli.PluginInfo, error) { - serverPlugins, err := GetInstalledServerPlugins() - if err != nil { - return nil, err - } +// Remove an installed standalone plugin if it is also installed as a server plugin, +// or vice versa if the TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS variable is enabled +func filterIdenticalStandaloneAndServerPlugins(standalonePlugins, serverPlugins []cli.PluginInfo) (installedStandalone, installedServer []cli.PluginInfo) { + standaloneOverServerPlugins, _ := strconv.ParseBool(os.Getenv(constants.ConfigVariableStandaloneOverContextPlugins)) - var installedStandalone []cli.PluginInfo - for i := range standalone { - found := false - for j := range serverPlugins { - if standalone[i].Name == serverPlugins[j].Name && standalone[i].Target == serverPlugins[j].Target { - found = true - break + if !standaloneOverServerPlugins { + installedServer = serverPlugins + + for i := range standalonePlugins { + found := false + for j := range serverPlugins { + if standalonePlugins[i].Name == serverPlugins[j].Name && standalonePlugins[i].Target == serverPlugins[j].Target { + found = true + break + } + } + if !found { + // No server plugin of the same name/target so we keep the standalone plugin + installedStandalone = append(installedStandalone, standalonePlugins[i]) } } - if !found { - installedStandalone = append(installedStandalone, standalone[i]) + } else { + installedStandalone = standalonePlugins + + for i := range serverPlugins { + found := false + for j := range standalonePlugins { + if serverPlugins[i].Name == standalonePlugins[j].Name && serverPlugins[i].Target == standalonePlugins[j].Target { + found = true + break + } + } + if !found { + // No standalone plugin of the same name/target so we keep the server plugin + installedServer = append(installedServer, serverPlugins[i]) + } } } - return installedStandalone, nil + + return installedStandalone, installedServer } diff --git a/pkg/pluginsupplier/plugin_supplier_test.go b/pkg/pluginsupplier/plugin_supplier_test.go index 9d0ca9039..07da48530 100644 --- a/pkg/pluginsupplier/plugin_supplier_test.go +++ b/pkg/pluginsupplier/plugin_supplier_test.go @@ -12,6 +12,7 @@ import ( "github.com/vmware-tanzu/tanzu-cli/pkg/catalog" "github.com/vmware-tanzu/tanzu-cli/pkg/cli" "github.com/vmware-tanzu/tanzu-cli/pkg/common" + "github.com/vmware-tanzu/tanzu-cli/pkg/constants" "github.com/vmware-tanzu/tanzu-plugin-runtime/config/types" "github.com/vmware-tanzu/tanzu-plugin-runtime/plugin" @@ -51,7 +52,7 @@ var _ = Describe("GetInstalledStandalonePlugins", func() { }) Context("when a standalone plugins installed", func() { BeforeEach(func() { - pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s) + pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -112,7 +113,7 @@ var _ = Describe("GetInstalledServerPlugins", func() { Context("when a server plugin for k8s target installed", func() { BeforeEach(func() { contextNameFromConfig := k8sContextName - pd1, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin1", types.TargetK8s) + pd1, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin1", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -126,7 +127,7 @@ var _ = Describe("GetInstalledServerPlugins", func() { Context("when a server plugin for tmc target installed", func() { BeforeEach(func() { contextNameFromConfig := tmcContextName - pd1, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetTMC) + pd1, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetTMC, "v1.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -140,11 +141,11 @@ var _ = Describe("GetInstalledServerPlugins", func() { Context("when a server plugin for both tmc and k8s targets installed", func() { BeforeEach(func() { contextNameFromConfig := k8sContextName - pd1, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin1", types.TargetTMC) + pd1, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin1", types.TargetTMC, "v1.0.0") Expect(err).ToNot(HaveOccurred()) contextNameFromConfig = tmcContextName - pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetTMC) + pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetTMC, "v1.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -160,14 +161,17 @@ var _ = Describe("GetInstalledServerPlugins", func() { var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", func() { var ( - cdir string - err error - configFile *os.File - configFileNG *os.File - pd1 *cli.PluginInfo - pd2 *cli.PluginInfo - pd3 *cli.PluginInfo - pd4 *cli.PluginInfo + cdir string + err error + configFile *os.File + configFileNG *os.File + pd1 *cli.PluginInfo + pd2 *cli.PluginInfo + pd3 *cli.PluginInfo + pd4 *cli.PluginInfo + pd5 *cli.PluginInfo + pd6 *cli.PluginInfo + originalVarValue string ) const ( tmcContextName = "test-tmc-context" @@ -189,6 +193,8 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu os.Setenv("TANZU_CONFIG_NEXT_GEN", configFileNG.Name()) err = copy.Copy(filepath.Join("..", "fakes", "config", "tanzu_config_ng.yaml"), configFileNG.Name()) Expect(err).To(BeNil(), "Error while coping tanzu config-ng file for testing") + + originalVarValue = os.Getenv(constants.ConfigVariableStandaloneOverContextPlugins) }) AfterEach(func() { os.RemoveAll(cdir) @@ -196,6 +202,8 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu os.Unsetenv("TANZU_CONFIG_NEXT_GEN") os.RemoveAll(configFile.Name()) os.RemoveAll(configFileNG.Name()) + + os.Setenv(constants.ConfigVariableStandaloneOverContextPlugins, originalVarValue) }) Context("when no standalone or server plugins installed", func() { @@ -208,7 +216,7 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu }) Context("when a standalone plugins installed", func() { BeforeEach(func() { - pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s) + pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -221,11 +229,11 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu }) Context("when a standalone and server plugin for k8s target installed", func() { BeforeEach(func() { - pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s) + pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) contextNameFromConfig := k8sContextName - pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetK8s) + pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -240,15 +248,15 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu Context("when a standalone plugin and server plugin for both tmc and k8s targets installed", func() { BeforeEach(func() { - pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s) + pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) contextNameFromConfig := k8sContextName - pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetK8s) + pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) contextNameFromConfig = tmcContextName - pd3, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin3", types.TargetTMC) + pd3, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin3", types.TargetTMC, "v1.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -266,11 +274,11 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu BeforeEach(func() { sharedPluginName := "fake-plugin" sharedPluginTarget := types.TargetK8s - pd1, err = fakeInstallPlugin("", sharedPluginName, sharedPluginTarget) + pd1, err = fakeInstallPlugin("", sharedPluginName, sharedPluginTarget, "v1.0.0") Expect(err).ToNot(HaveOccurred()) contextNameFromConfig := k8sContextName - pd2, err = fakeInstallPlugin(contextNameFromConfig, sharedPluginName, sharedPluginTarget) + pd2, err = fakeInstallPlugin(contextNameFromConfig, sharedPluginName, sharedPluginTarget, "v2.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -280,29 +288,38 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu Expect(len(installedPlugins)).To(Equal(1)) Expect(installedPlugins).Should(ContainElement(*pd2)) }) + It("if TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS=1 it should return the standalone plugin only", func() { + err := os.Setenv(constants.ConfigVariableStandaloneOverContextPlugins, "1") + Expect(err).ToNot(HaveOccurred()) + + installedPlugins, err := GetInstalledPlugins() + Expect(err).ToNot(HaveOccurred()) + Expect(len(installedPlugins)).To(Equal(1)) + Expect(installedPlugins).Should(ContainElement(*pd1)) + }) }) Context("when multiple standalone plugins and server plugins are installed with some overlap", func() { BeforeEach(func() { - pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s) + pd1, err = fakeInstallPlugin("", "fake-server-plugin1", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) contextNameFromConfig := k8sContextName - pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetK8s) + pd2, err = fakeInstallPlugin(contextNameFromConfig, "fake-server-plugin2", types.TargetK8s, "v1.0.0") Expect(err).ToNot(HaveOccurred()) sharedPluginName := "fake-plugin1" sharedPluginTarget := types.TargetK8s - _, err = fakeInstallPlugin("", sharedPluginName, sharedPluginTarget) + pd3, err = fakeInstallPlugin("", sharedPluginName, sharedPluginTarget, "v1.0.0") Expect(err).ToNot(HaveOccurred()) - pd3, err = fakeInstallPlugin(contextNameFromConfig, sharedPluginName, sharedPluginTarget) + pd4, err = fakeInstallPlugin(contextNameFromConfig, sharedPluginName, sharedPluginTarget, "v2.0.0") Expect(err).ToNot(HaveOccurred()) sharedPluginName = "fake-plugin2" sharedPluginTarget = types.TargetTMC - _, err = fakeInstallPlugin("", sharedPluginName, sharedPluginTarget) + pd5, err = fakeInstallPlugin("", sharedPluginName, sharedPluginTarget, "v1.0.0") Expect(err).ToNot(HaveOccurred()) - pd4, err = fakeInstallPlugin(contextNameFromConfig, sharedPluginName, sharedPluginTarget) + pd6, err = fakeInstallPlugin(contextNameFromConfig, sharedPluginName, sharedPluginTarget, "v2.0.0") Expect(err).ToNot(HaveOccurred()) }) @@ -312,8 +329,19 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu Expect(len(installedPlugins)).To(Equal(4)) Expect(installedPlugins).Should(ContainElement(*pd1)) Expect(installedPlugins).Should(ContainElement(*pd2)) - Expect(installedPlugins).Should(ContainElement(*pd3)) Expect(installedPlugins).Should(ContainElement(*pd4)) + Expect(installedPlugins).Should(ContainElement(*pd6)) + }) + It("if TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS=1 it should not return any server plugins that are also standalone plugins", func() { + err := os.Setenv(constants.ConfigVariableStandaloneOverContextPlugins, "1") + Expect(err).ToNot(HaveOccurred()) + installedPlugins, err := GetInstalledPlugins() + Expect(err).ToNot(HaveOccurred()) + Expect(len(installedPlugins)).To(Equal(4)) + Expect(installedPlugins).Should(ContainElement(*pd1)) + Expect(installedPlugins).Should(ContainElement(*pd2)) + Expect(installedPlugins).Should(ContainElement(*pd3)) + Expect(installedPlugins).Should(ContainElement(*pd5)) }) }) Context("with a catalog cache from an older CLI version", func() { @@ -433,15 +461,15 @@ var _ = Describe("GetInstalledPlugins (both standalone and context plugins)", fu }) }) -func fakeInstallPlugin(contextName, pluginName string, target types.Target) (*cli.PluginInfo, error) { +func fakeInstallPlugin(contextName, pluginName string, target types.Target, version string) (*cli.PluginInfo, error) { cc, err := catalog.NewContextCatalog(contextName) if err != nil { return nil, err } pi := &cli.PluginInfo{ Name: pluginName, - InstallationPath: "/path/to/plugin/" + pluginName, - Version: "1.0.0", + InstallationPath: "/path/to/plugin/" + pluginName + "/" + version, + Version: version, Hidden: true, Target: target, DefaultFeatureFlags: map[string]bool{