From e58239bda922075a9bad8aad028e2815643337c2 Mon Sep 17 00:00:00 2001 From: Prem Kumar Kalle Date: Thu, 30 Mar 2023 17:07:06 -0700 Subject: [PATCH] Add CEIP Opt-In support - User would be prompted for CEIP Opt-In on any first tanzu command. Users choice would be persisted in the tanzu configuration file. Users can skip the prompt by providing their choice(yes/no) using the environment variable (TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER) on the first use - Also added commands(experimental) "tanzu ceip-participation set" and "tanzu ceip-participation get" to get and set the CEIP Opt-In status Signed-off-by: Prem Kumar Kalle --- Makefile | 4 + go.mod | 2 +- go.sum | 4 +- pkg/command/ceip_participation.go | 93 ++++++++++++++++++++ pkg/command/ceip_participation_test.go | 117 +++++++++++++++++++++++++ pkg/command/plugin_test.go | 9 +- pkg/command/root.go | 15 +++- pkg/command/root_test.go | 2 + pkg/config/config.go | 58 ++++++++++++ pkg/constants/env_variables.go | 1 + 10 files changed, 300 insertions(+), 5 deletions(-) create mode 100644 pkg/command/ceip_participation.go create mode 100644 pkg/command/ceip_participation_test.go diff --git a/Makefile b/Makefile index 06d7e74f0..0ead4ff26 100644 --- a/Makefile +++ b/Makefile @@ -195,6 +195,7 @@ test: fmt ## Run Tests .PHONY: e2e-cli-core ## Execute all CLI Core E2E Tests e2e-cli-core: e2e-cli-plugin-compatibility-test e2e-cli-tmc-test e2e-cli-plugin-lifecycle-test + export TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER="Yes" ; \ ${GO} test `go list ./test/e2e/... | grep -v test/e2e/context/tmc | grep -v test/e2e/plugins_compatibility | grep -v test/e2e/plugin_lifecycle` -timeout 60m -race -coverprofile coverage.txt ${GOTEST_VERBOSE} .PHONY: e2e-cli-plugin-compatibility-test ## Execute CLI Core Plugin Compatibility E2E test cases @@ -204,6 +205,7 @@ e2e-cli-plugin-compatibility-test: else \ export TANZU_CLI_PRE_RELEASE_REPO_IMAGE=$(TANZU_CLI_E2E_TEST_CENTRAL_REPO_URL) ; \ export TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_VERIFICATION_SKIP_LIST=$(TANZU_CLI_E2E_TEST_CENTRAL_REPO_URL) ; \ + export TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER="Yes" ; \ ${GO} test ./test/e2e/plugins_compatibility -timeout 60m -race -coverprofile coverage.txt ${GOTEST_VERBOSE} ; \ fi @@ -215,6 +217,7 @@ e2e-cli-plugin-lifecycle-test: export TANZU_CLI_E2E_TEST_LOCAL_CENTRAL_REPO_URL=$(TANZU_CLI_E2E_TEST_LOCAL_CENTRAL_REPO_URL) ; \ export TANZU_CLI_PRE_RELEASE_REPO_IMAGE=$(TANZU_CLI_E2E_TEST_LOCAL_CENTRAL_REPO_URL) ; \ export TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_VERIFICATION_SKIP_LIST=$(TANZU_CLI_E2E_TEST_LOCAL_CENTRAL_REPO_URL) ; \ + export TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER="Yes" ; \ ${GO} test ./test/e2e/plugin_lifecycle -timeout 60m -race -coverprofile coverage.txt ${GOTEST_VERBOSE} ; \ fi @@ -223,6 +226,7 @@ e2e-cli-tmc-test: @if [ "${TANZU_API_TOKEN}" = "" ] && [ "$(TANZU_CLI_TMC_UNSTABLE_URL)" = "" ]; then \ echo "***Skipping TMC specific e2e tests cases because environment variables TANZU_API_TOKEN and TANZU_CLI_TMC_UNSTABLE_URL are not set***" ; \ else \ + export TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER="Yes" ; \ ${GO} test ./test/e2e/context/tmc -timeout 60m -race -coverprofile coverage.txt ${GOTEST_VERBOSE} ; \ fi diff --git a/go.mod b/go.mod index 2f46d7d0c..951365ab2 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/tj/assert v0.0.3 github.com/vmware-tanzu/carvel-ytt v0.40.0 github.com/vmware-tanzu/tanzu-framework/capabilities/client v0.0.0-20230130173350-eeda69d80a24 - github.com/vmware-tanzu/tanzu-plugin-runtime v0.0.2-0.20230321210330-330c29284da6 + github.com/vmware-tanzu/tanzu-plugin-runtime v0.0.2-0.20230403161015-4575b79c9655 go.pinniped.dev v0.20.0 go.uber.org/multierr v1.8.0 golang.org/x/mod v0.8.0 diff --git a/go.sum b/go.sum index 5a4533019..d6d25cef6 100644 --- a/go.sum +++ b/go.sum @@ -1286,8 +1286,8 @@ github.com/vmware-tanzu/tanzu-framework/apis/run v0.0.0-20221207131309-7323ca04b github.com/vmware-tanzu/tanzu-framework/apis/run v0.0.0-20221207131309-7323ca04b86c/go.mod h1:ukZpKQ0hf5bjWdJLjn2M6qXP+9giZWQPxt8nOfrCR+o= github.com/vmware-tanzu/tanzu-framework/capabilities/client v0.0.0-20230130173350-eeda69d80a24 h1:zz3XDCLPqvhtx8OMMRNjSn/krxhqkYnuw9Z9DBh6ruA= github.com/vmware-tanzu/tanzu-framework/capabilities/client v0.0.0-20230130173350-eeda69d80a24/go.mod h1:rcIfoGpdav3evsyEMMzYH0xhGZOkIy+Ra3koypM8Aco= -github.com/vmware-tanzu/tanzu-plugin-runtime v0.0.2-0.20230321210330-330c29284da6 h1:w+lpvLqFtRr0NpmAHtRnMi7hLOG8x0DZ8sE+iVX+RRs= -github.com/vmware-tanzu/tanzu-plugin-runtime v0.0.2-0.20230321210330-330c29284da6/go.mod h1:y70TLdev7MX8K6CkAA7h92qVUDyjbX8y9/J5q4UmhRs= +github.com/vmware-tanzu/tanzu-plugin-runtime v0.0.2-0.20230403161015-4575b79c9655 h1:ZAkEx5/PIwVuapIJ1IH4QJCjLX2TXGXOu1CaO9is4t4= +github.com/vmware-tanzu/tanzu-plugin-runtime v0.0.2-0.20230403161015-4575b79c9655/go.mod h1:y70TLdev7MX8K6CkAA7h92qVUDyjbX8y9/J5q4UmhRs= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.73.1 h1:UMagqUZLJdjss1SovIC+kJCH4k2AZWXl58gJd38Y/hI= github.com/xanzy/go-gitlab v0.73.1/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= diff --git a/pkg/command/ceip_participation.go b/pkg/command/ceip_participation.go new file mode 100644 index 000000000..733ccb47e --- /dev/null +++ b/pkg/command/ceip_participation.go @@ -0,0 +1,93 @@ +// Copyright 2023 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package command + +import ( + "strconv" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/vmware-tanzu/tanzu-cli/pkg/cli" + "github.com/vmware-tanzu/tanzu-plugin-runtime/component" + configlib "github.com/vmware-tanzu/tanzu-plugin-runtime/config" + "github.com/vmware-tanzu/tanzu-plugin-runtime/plugin" +) + +// CeipOptOutStatus and CeipOptInStatus are constants for the CEIP opt-in/out verbiage +const ( + CeipOptInStatus = "Opt-in" + CeipOptOutStatus = "Opt-out" +) + +// Note(TODO:prkalle): The below ceip-participation command(experimental) added may be removed in the next release, +// If we decide to fold this functionality into existing 'tanzu telemetry' plugin + +func newCEIPParticipationCmd() *cobra.Command { + var ceipParticipationCmd = &cobra.Command{ + Use: "ceip-participation", + Short: "Manage CEIP Participation", + Long: "Manage CEIP Participation", + Aliases: []string{"ceip"}, + Annotations: map[string]string{ + "group": string(plugin.SystemCmdGroup), + }, + } + ceipParticipationCmd.SetUsageFunc(cli.SubCmdUsageFunc) + + ceipParticipationCmd.AddCommand( + newCEIPParticipationSetCmd(), + newCEIPParticipationGetCmd(), + ) + + return ceipParticipationCmd +} + +func newCEIPParticipationSetCmd() *cobra.Command { + var setCmd = &cobra.Command{ + Use: "set OPT_IN_BOOL", + Short: "Set the opt-in preference for CEIP", + Long: "Set the opt-in preference for CEIP", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if !strings.EqualFold(args[0], "true") && !strings.EqualFold(args[0], "false") { + return errors.Errorf("incorrect boolean argument: %q", args[0]) + } + err := configlib.SetCEIPOptIn(strconv.FormatBool(strings.EqualFold(args[0], "true"))) + if err != nil { + return errors.Wrapf(err, "failed to update the configuration") + } + return nil + }, + } + + return setCmd +} + +func newCEIPParticipationGetCmd() *cobra.Command { + var getCmd = &cobra.Command{ + Use: "get", + Short: "Get the current CEIP opt-in status", + Long: "Get the current CEIP opt-in status", + RunE: func(cmd *cobra.Command, args []string) error { + optInVal, err := configlib.GetCEIPOptIn() + if err != nil { + return errors.Wrapf(err, "failed to get the CEIP opt-in status") + } + ceipStatus := "" + if optInVal == "true" { + ceipStatus = CeipOptInStatus + } else { + ceipStatus = CeipOptOutStatus + } + t := component.NewOutputWriter(cmd.OutOrStdout(), outputFormat, "CEIP-Status") + t.AddRow(ceipStatus) + t.Render() + return nil + }, + } + + return getCmd +} diff --git a/pkg/command/ceip_participation_test.go b/pkg/command/ceip_participation_test.go new file mode 100644 index 000000000..c337b1cb5 --- /dev/null +++ b/pkg/command/ceip_participation_test.go @@ -0,0 +1,117 @@ +// Copyright 2023 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package command + +import ( + "bytes" + "os" + "path/filepath" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/otiai10/copy" +) + +var _ = Describe("ceip-participation command tests", func() { + + Describe("ceip-participation command set/get tests", func() { + var ( + tkgConfigFile *os.File + tkgConfigFileNG *os.File + err error + ) + + BeforeEach(func() { + tkgConfigFile, err = os.CreateTemp("", "config") + Expect(err).To(BeNil()) + err = copy.Copy(filepath.Join("..", "fakes", "config", "tanzu_config.yaml"), tkgConfigFile.Name()) + Expect(err).To(BeNil(), "Error while copying tanzu config file for testing") + os.Setenv("TANZU_CONFIG", tkgConfigFile.Name()) + + tkgConfigFileNG, err = os.CreateTemp("", "config_ng") + Expect(err).To(BeNil()) + os.Setenv("TANZU_CONFIG_NEXT_GEN", tkgConfigFileNG.Name()) + err = copy.Copy(filepath.Join("..", "fakes", "config", "tanzu_config_ng.yaml"), tkgConfigFileNG.Name()) + Expect(err).To(BeNil(), "Error while copying tanzu config_ng.yaml file for testing") + }) + AfterEach(func() { + os.Unsetenv("TANZU_CONFIG") + os.Unsetenv("TANZU_CONFIG_NEXT_GEN") + os.RemoveAll(tkgConfigFile.Name()) + os.RemoveAll(tkgConfigFileNG.Name()) + resetLoginCommandFlags() + }) + Context("ceip-participation set to true", func() { + It("ceip-participation set should be successful and get should return status as 'Opt-in' status", func() { + ceipSetCmd := newCEIPParticipationSetCmd() + ceipSetCmd.SetArgs([]string{"true"}) + err = ceipSetCmd.Execute() + Expect(err).To(BeNil()) + + ceipGetCmd := newCEIPParticipationGetCmd() + var out bytes.Buffer + ceipGetCmd.SetOut(&out) + err = ceipGetCmd.Execute() + Expect(err).To(BeNil()) + Expect(out.String()).To(ContainSubstring("Opt-in")) + + ceipSetCmd = newCEIPParticipationSetCmd() + ceipSetCmd.SetArgs([]string{"True"}) + err = ceipSetCmd.Execute() + Expect(err).To(BeNil()) + + ceipGetCmd = newCEIPParticipationGetCmd() + out.Reset() + ceipGetCmd.SetOut(&out) + err = ceipGetCmd.Execute() + Expect(err).To(BeNil()) + Expect(out.String()).To(ContainSubstring("Opt-in")) + + }) + }) + Context("ceip-participation set to false", func() { + It("ceip-participation set should be successful and get should return status as 'Opt-out' status", func() { + ceipSetCmd := newCEIPParticipationSetCmd() + ceipSetCmd.SetArgs([]string{"false"}) + err = ceipSetCmd.Execute() + Expect(err).To(BeNil()) + + ceipGetCmd := newCEIPParticipationGetCmd() + var out bytes.Buffer + ceipGetCmd.SetOut(&out) + err = ceipGetCmd.Execute() + Expect(err).To(BeNil()) + Expect(out.String()).To(ContainSubstring("Opt-out")) + + ceipSetCmd = newCEIPParticipationSetCmd() + ceipSetCmd.SetArgs([]string{"False"}) + err = ceipSetCmd.Execute() + Expect(err).To(BeNil()) + + ceipGetCmd = newCEIPParticipationGetCmd() + out.Reset() + ceipGetCmd.SetOut(&out) + err = ceipGetCmd.Execute() + Expect(err).To(BeNil()) + Expect(out.String()).To(ContainSubstring("Opt-out")) + }) + }) + Context("ceip-participation set to invalid boolean argument", func() { + It("ceip-participation set should fail", func() { + ceipSetCmd := newCEIPParticipationSetCmd() + ceipSetCmd.SetArgs([]string{"fakebool"}) + err = ceipSetCmd.Execute() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("incorrect boolean argument:")) + }) + }) + Context("ceip-participation set without argument", func() { + It("ceip-participation set should fail", func() { + ceipSetCmd := newCEIPParticipationSetCmd() + err = ceipSetCmd.Execute() + Expect(err).To(HaveOccurred()) + }) + }) + }) + +}) diff --git a/pkg/command/plugin_test.go b/pkg/command/plugin_test.go index 54d77a8e5..8a8cdef87 100644 --- a/pkg/command/plugin_test.go +++ b/pkg/command/plugin_test.go @@ -148,6 +148,7 @@ func TestPluginList(t *testing.T) { assert.Nil(t, err) defer os.RemoveAll(dir) os.Setenv("TEST_CUSTOM_CATALOG_CACHE_DIR", dir) + os.Setenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER", "No") // Always turn on the context feature featureArray := strings.Split(constants.FeatureContextCommand, ".") @@ -209,6 +210,7 @@ func TestPluginList(t *testing.T) { os.Unsetenv("TEST_CUSTOM_CATALOG_CACHE_DIR") os.Unsetenv("TANZU_CONFIG") os.Unsetenv("TANZU_CONFIG_NEXT_GEN") + os.Unsetenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER") } } @@ -245,7 +247,7 @@ func TestDeletePlugin(t *testing.T) { assert.Nil(t, err) defer os.RemoveAll(dir) os.Setenv("TEST_CUSTOM_CATALOG_CACHE_DIR", dir) - + os.Setenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER", "No") var completionType uint8 t.Run(spec.test, func(t *testing.T) { assert := assert.New(t) @@ -284,6 +286,7 @@ func TestDeletePlugin(t *testing.T) { } }) os.Unsetenv("TEST_CUSTOM_CATALOG_CACHE_DIR") + os.Unsetenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER") } } @@ -355,6 +358,7 @@ func TestInstallPlugin(t *testing.T) { tkgConfigFileNG, err := os.CreateTemp("", "config_ng") assert.Nil(err) os.Setenv("TANZU_CONFIG_NEXT_GEN", tkgConfigFileNG.Name()) + os.Setenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER", "No") // Bypass the environment variable for testing err = os.Setenv(constants.ConfigVariablePreReleasePluginRepoImage, pluginmanager.PreReleasePluginRepoImageBypass) @@ -367,6 +371,7 @@ func TestInstallPlugin(t *testing.T) { defer func() { os.Unsetenv("TANZU_CONFIG") os.Unsetenv("TANZU_CONFIG_NEXT_GEN") + os.Unsetenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER") os.RemoveAll(tkgConfigFile.Name()) os.RemoveAll(tkgConfigFileNG.Name()) }() @@ -415,6 +420,7 @@ func TestUpgradePlugin(t *testing.T) { tkgConfigFileNG, err := os.CreateTemp("", "config_ng") assert.Nil(err) os.Setenv("TANZU_CONFIG_NEXT_GEN", tkgConfigFileNG.Name()) + os.Setenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER", "No") featureArray := strings.Split(constants.FeatureContextCommand, ".") err = config.SetFeature(featureArray[1], featureArray[2], "true") @@ -423,6 +429,7 @@ func TestUpgradePlugin(t *testing.T) { defer func() { os.Unsetenv("TANZU_CONFIG") os.Unsetenv("TANZU_CONFIG_NEXT_GEN") + os.Unsetenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER") os.RemoveAll(tkgConfigFile.Name()) os.RemoveAll(tkgConfigFileNG.Name()) }() diff --git a/pkg/command/root.go b/pkg/command/root.go index 794b3e4bc..32f22d87f 100644 --- a/pkg/command/root.go +++ b/pkg/command/root.go @@ -33,8 +33,18 @@ func NewRootCmd() (*cobra.Command, error) { SilenceUsage: true, // Flag parsing must be deactivated because the root plugin won't know about all flags. DisableFlagParsing: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if cmd.Name() != cobra.ShellCompRequestCmd && cmd.Name() != completionCmd.Name() { + // Configure CEIP setting but not if we are doing shell completion stuff. + // The shell completion setup is not interactive, so it should not trigger + // the ceip prompt. + if err := cliconfig.ConfigureCEIPOptIn(); err != nil { + return err + } + } + return nil + }, } - uFunc := cli.NewMainUsage().UsageFunc() rootCmd.SetUsageFunc(uFunc) @@ -49,6 +59,9 @@ func NewRootCmd() (*cobra.Command, error) { completionCmd, configCmd, genAllDocsCmd, + // Note(TODO:prkalle): The below ceip-participation command(experimental) added may be removed in the next release, + // If we decide to fold this functionality into existing 'tanzu telemetry' plugin + newCEIPParticipationCmd(), ) // If the context and target feature is enabled, add the corresponding commands under root. diff --git a/pkg/command/root_test.go b/pkg/command/root_test.go index eb3bc3a14..cb2c2c255 100644 --- a/pkg/command/root_test.go +++ b/pkg/command/root_test.go @@ -318,6 +318,7 @@ func TestEnvVarsSet(t *testing.T) { defer os.RemoveAll(configFile.Name()) configFileNG, _ := os.CreateTemp("", "config_ng") os.Setenv("TANZU_CONFIG_NEXT_GEN", configFileNG.Name()) + os.Setenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER", "No") defer os.RemoveAll(configFileNG.Name()) // Setup default feature flags since we have created new config files @@ -353,6 +354,7 @@ func TestEnvVarsSet(t *testing.T) { // Cleanup os.Unsetenv("TANZU_CONFIG") os.Unsetenv("TANZU_CONFIG_NEXT_GEN") + os.Unsetenv("TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER") os.Unsetenv(envVarName) } diff --git a/pkg/config/config.go b/pkg/config/config.go index f6106971a..754fdd851 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -6,12 +6,27 @@ package config import ( "os" + "strconv" + "strings" + "github.com/pkg/errors" + + "github.com/vmware-tanzu/tanzu-plugin-runtime/component" + configlib "github.com/vmware-tanzu/tanzu-plugin-runtime/config" + + "github.com/vmware-tanzu/tanzu-cli/pkg/constants" "github.com/vmware-tanzu/tanzu-cli/pkg/interfaces" ) var ( configClient interfaces.ConfigClientWrapper + // TODO(prkalle): Update the below CEIP message if necessary + ceipPromptMsg = ` +VMware's Customer Experience Improvement Program ("CEIP") provides VMware with information that enables VMware to improve its products and services and fix problems. By choosing to participate in CEIP, you agree that VMware may collect technical information about your use of VMware products and services on a regular basis. This information does not personally identify you. +For more details about the Program, please see http://www.vmware.com/trustvmware/ceip.html + +Do you agree to Participate in the Customer Experience Improvement Program? +` ) func init() { @@ -33,3 +48,46 @@ func ConfigureEnvVariables() { } } } + +// ConfigureCEIPOptIn checks and configures the User CEIP Opt-in choice in the tanzu configuration file +func ConfigureCEIPOptIn() error { + ceipOptInConfigVal, _ := configlib.GetCEIPOptIn() + // If CEIP Opt-In config parameter is already set, do nothing + if ceipOptInConfigVal != "" { + return nil + } + + ceipOptInUserVal, err := getCEIPUserOptIn() + if err != nil { + return errors.Wrapf(err, "failed to get CEIP Opt-In status") + } + + err = configlib.SetCEIPOptIn(strconv.FormatBool(ceipOptInUserVal)) + if err != nil { + return errors.Wrapf(err, "failed to update the CEIP Opt-In status") + } + + return nil +} + +func getCEIPUserOptIn() (bool, error) { + var ceipOptIn string + optInPromptChoiceEnvVal := os.Getenv(constants.CEIPOptInUserPromptAnswer) + if optInPromptChoiceEnvVal != "" { + return strings.EqualFold(optInPromptChoiceEnvVal, "Yes"), nil + } + + // prompt user and record their choice + err := component.Prompt( + &component.PromptConfig{ + Message: ceipPromptMsg, + Options: []string{"Yes", "No"}, + Default: "Yes", + }, + &ceipOptIn, + ) + if err != nil { + return false, err + } + return (ceipOptIn == "Yes"), nil +} diff --git a/pkg/constants/env_variables.go b/pkg/constants/env_variables.go index 3a81b6d42..ba05f0fab 100644 --- a/pkg/constants/env_variables.go +++ b/pkg/constants/env_variables.go @@ -16,4 +16,5 @@ const ( PluginDiscoveryImageSignatureVerificationSkipList = "TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_VERIFICATION_SKIP_LIST" PublicKeyPathForPluginDiscoveryImageSignature = "TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_PUBLIC_KEY_PATH" SuppressSkipSignatureVerificationWarning = "TANZU_CLI_SUPPRESS_SKIP_SIGNATURE_VERIFICATION_WARNING" + CEIPOptInUserPromptAnswer = "TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER" )