From 1bf9f61ee5ebf2d0a864a091d0ac68afd78e1428 Mon Sep 17 00:00:00 2001 From: Brian Strauch Date: Fri, 27 Aug 2021 11:05:55 -0700 Subject: [PATCH] CLI Unification: Warn if command is unavailable (#962) * show warnings for unavailable top level commands * fix int tests * show warnings for unavailable internal commands * warnings for schema-registry commands * warnings for kafka commands and fix tests * fix typo * remove todo * fix iam * fix cluster * fix lint * fix int tests * fix prerun logic * unit tests * fix prerun logic * code review * fix tests * fix windows test * updated jenkinsfile git branch * fix unit test * disallow auto-login to opposite context * fix windows tests * change auto-login logic * add cloud-signup suggestions * fix windows tests * use 7.0.x branch of muckrake * Revert "use 7.0.x branch of muckrake" This reverts commit ee4b874e4c09c722412b64d1508e19948972ee47. --- Jenkinsfile | 4 +- internal/cmd/admin/command.go | 7 +- internal/cmd/api-key/command.go | 5 +- internal/cmd/audit-log/command.go | 38 ++--- internal/cmd/audit-log/command_config.go | 23 +-- internal/cmd/audit-log/command_describe.go | 7 +- .../cmd/audit-log/command_describe_test.go | 2 +- internal/cmd/audit-log/command_migrate.go | 15 +- internal/cmd/audit-log/command_route.go | 21 +-- internal/cmd/audit-log/command_test.go | 2 +- internal/cmd/cluster/command.go | 5 +- internal/cmd/command.go | 99 ++++++----- internal/cmd/command_test.go | 10 +- internal/cmd/config/context.go | 2 +- internal/cmd/connect/command.go | 92 ++++++----- .../cmd/connect/command_cluster_onprem.go | 5 +- internal/cmd/connect/command_event.go | 5 +- internal/cmd/connect/command_plugin.go | 5 +- internal/cmd/environment/command.go | 5 +- internal/cmd/iam/command.go | 29 ++-- internal/cmd/iam/command_acl.go | 11 +- internal/cmd/iam/command_role.go | 8 +- internal/cmd/iam/command_rolebinding.go | 14 +- internal/cmd/init/command.go | 3 +- internal/cmd/kafka/command.go | 47 ++---- internal/cmd/kafka/command_acl.go | 31 +++- internal/cmd/kafka/command_acl_onprem.go | 43 ++--- ...nd_cluster_cloud.go => command_cluster.go} | 89 ++++++---- internal/cmd/kafka/command_cluster_onprem.go | 39 +---- ..._cloud_test.go => command_cluster_test.go} | 3 +- internal/cmd/kafka/command_link.go | 26 +-- internal/cmd/kafka/command_mirror.go | 34 ++-- internal/cmd/kafka/command_region.go | 7 +- internal/cmd/kafka/command_topic.go | 51 ++++-- internal/cmd/kafka/command_topic_onprem.go | 65 +++----- .../cmd/kafka/command_topic_onprem_test.go | 12 +- internal/cmd/kafka/command_topic_test.go | 2 +- internal/cmd/ksql/command.go | 16 +- internal/cmd/ksql/command_cluster.go | 5 +- internal/cmd/ksql/command_cluster_onprem.go | 5 +- internal/cmd/logout/command.go | 6 +- internal/cmd/price/command.go | 17 +- internal/cmd/prompt/command.go | 13 +- internal/cmd/schema-registry/command.go | 15 +- .../cmd/schema-registry/command_cluster.go | 125 ++++++++------ .../schema-registry/command_cluster_onprem.go | 46 ++---- .../cmd/schema-registry/command_schema.go | 5 +- .../cmd/schema-registry/command_subject.go | 5 +- internal/cmd/secret/command.go | 25 +-- internal/cmd/service-account/command.go | 5 +- internal/cmd/shell/command.go | 9 +- internal/cmd/update/command.go | 30 ++-- internal/pkg/analytics/analytics.go | 6 +- internal/pkg/cmd/prerunner.go | 156 ++++++++++-------- internal/pkg/cmd/run_requirements.go | 88 ++++++++++ internal/pkg/cmd/run_requirements_test.go | 114 +++++++++++++ internal/pkg/config/v3/config.go | 53 +++--- internal/pkg/config/v3/config_test.go | 16 +- internal/pkg/config/v3/context.go | 12 +- mock/commander.go | 16 +- .../output/help/help-cloud-windows.golden | 1 - test/fixtures/output/help/help-cloud.golden | 1 - .../help/help-no-context-windows.golden | 19 +-- .../output/help/help-no-context.golden | 21 ++- 64 files changed, 957 insertions(+), 739 deletions(-) rename internal/cmd/kafka/{command_cluster_cloud.go => command_cluster.go} (90%) rename internal/cmd/kafka/{command_cluster_cloud_test.go => command_cluster_test.go} (99%) create mode 100644 internal/pkg/cmd/run_requirements.go create mode 100644 internal/pkg/cmd/run_requirements_test.go diff --git a/Jenkinsfile b/Jenkinsfile index a44d08f522..6bcba363a3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -81,7 +81,7 @@ def job = { export confluent_s3="https://s3-us-west-2.amazonaws.com" git clone git@github.com:confluentinc/muckrake.git cd muckrake - git checkout 6.2.x + git checkout 6.2.x-cli-unification sed -i "s?\\(confluent-cli-\\(.*\\)=\\)\\(.*\\)?\\1${confluent_s3}/confluent.cloud/confluent-cli-system-test-builds/confluent_SNAPSHOT-${HASH}_linux_amd64\\.tar\\.gz\\"?" ducker/ducker sed -i "s?get_cli .*?& ${confluent_s3}/confluent.cloud/confluent-cli-system-test-builds/confluent_SNAPSHOT-${HASH}_linux_amd64\\.tar\\.gz?g" vagrant/base-ubuntu.sh sed -i "s?get_cli .*?& ${confluent_s3}/confluent.cloud/confluent-cli-system-test-builds/confluent_SNAPSHOT-${HASH}_linux_amd64\\.tar\\.gz?g" vagrant/base-redhat.sh @@ -106,7 +106,7 @@ def job = { ["sonatype/confluent", "user", "SONATYPE_OSSRH_USER"], ["sonatype/confluent", "password", "SONATYPE_OSSRH_PASSWORD"]]) { withEnv(["GIT_CREDENTIAL=${env.GIT_USER}:${env.GIT_TOKEN}", - "AWS_KEYPAIR_FILE=${pem_file}", "GIT_BRANCH=6.2.x"]) { + "AWS_KEYPAIR_FILE=${pem_file}", "GIT_BRANCH=6.2.x-cli-unification"]) { withVaultFile([["maven/jenkins_maven_global_settings", "settings_xml", "/home/jenkins/.m2/settings.xml", "MAVEN_GLOBAL_SETTINGS_FILE"], ["gradle/gradle_properties_maven", "gradle_properties_file", diff --git a/internal/cmd/admin/command.go b/internal/cmd/admin/command.go index 4881a46aab..7e8fcd6c0b 100644 --- a/internal/cmd/admin/command.go +++ b/internal/cmd/admin/command.go @@ -9,9 +9,10 @@ import ( func New(prerunner pcmd.PreRunner, isTest bool) *cobra.Command { c := pcmd.NewAnonymousCLICommand( &cobra.Command{ - Use: "admin", - Short: "Perform administrative tasks for the current organization.", - Args: cobra.NoArgs, + Use: "admin", + Short: "Perform administrative tasks for the current organization.", + Args: cobra.NoArgs, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, }, prerunner, ) diff --git a/internal/cmd/api-key/command.go b/internal/cmd/api-key/command.go index d730ba2d54..fbb6c9f04a 100644 --- a/internal/cmd/api-key/command.go +++ b/internal/cmd/api-key/command.go @@ -66,8 +66,9 @@ var ( func New(prerunner pcmd.PreRunner, keystore keystore.KeyStore, resolver pcmd.FlagResolver, analyticsClient analytics.Client) *command { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "api-key", - Short: "Manage the API keys.", + Use: "api-key", + Short: "Manage the API keys.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner, SubcommandFlags) cmd := &command{ AuthenticatedStateFlagCommand: cliCmd, diff --git a/internal/cmd/audit-log/command.go b/internal/cmd/audit-log/command.go index 13f970448c..ec0f7fa042 100644 --- a/internal/cmd/audit-log/command.go +++ b/internal/cmd/audit-log/command.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/cobra" pcmd "github.com/confluentinc/cli/internal/pkg/cmd" - v3 "github.com/confluentinc/cli/internal/pkg/config/v3" "github.com/confluentinc/cli/internal/pkg/errors" ) @@ -19,29 +18,28 @@ type command struct { } // New returns the default command object for interacting with audit logs. -func New(cfg *v3.Config, prerunner pcmd.PreRunner) *cobra.Command { - cliCmd := pcmd.NewCLICommand( - &cobra.Command{ - Use: "audit-log", - Short: "Manage audit log configuration.", - Long: "Manage which auditable events are logged, and where the event logs are sent.", - }, prerunner) - cmd := &command{ - CLICommand: cliCmd, +func New(prerunner pcmd.PreRunner) *cobra.Command { + cmd := &cobra.Command{ + Use: "audit-log", + Short: "Manage audit log configuration.", + Long: "Manage which auditable events are logged, and where the event logs are sent.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLoginOrOnPremLogin}, + } + + c := &command{ + CLICommand: pcmd.NewAnonymousCLICommand(cmd, prerunner), prerunner: prerunner, } - cmd.init(cfg) - return cmd.Command + c.init() + + return c.Command } -func (c *command) init(cfg *v3.Config) { - if cfg.IsCloud() { - c.AddCommand(NewDescribeCommand(c.prerunner)) - } else { - c.AddCommand(NewMigrateCommand(c.prerunner)) - c.AddCommand(NewConfigCommand(c.prerunner)) - c.AddCommand(NewRouteCommand(c.prerunner)) - } +func (c *command) init() { + c.AddCommand(NewDescribeCommand(c.prerunner)) + c.AddCommand(NewMigrateCommand(c.prerunner)) + c.AddCommand(NewConfigCommand(c.prerunner)) + c.AddCommand(NewRouteCommand(c.prerunner)) } type errorMessage struct { diff --git a/internal/cmd/audit-log/command_config.go b/internal/cmd/audit-log/command_config.go index 39db8883f4..29a0eddc8f 100644 --- a/internal/cmd/audit-log/command_config.go +++ b/internal/cmd/audit-log/command_config.go @@ -12,21 +12,22 @@ import ( mds "github.com/confluentinc/mds-sdk-go/mdsv1" "github.com/spf13/cobra" - "github.com/confluentinc/cli/internal/pkg/cmd" + pcmd "github.com/confluentinc/cli/internal/pkg/cmd" ) type configCommand struct { - *cmd.AuthenticatedStateFlagCommand - prerunner cmd.PreRunner + *pcmd.AuthenticatedStateFlagCommand + prerunner pcmd.PreRunner } // NewRouteCommand returns the sub-command object for interacting with audit log route rules. -func NewConfigCommand(prerunner cmd.PreRunner) *cobra.Command { - cliCmd := cmd.NewAuthenticatedWithMDSStateFlagCommand( +func NewConfigCommand(prerunner pcmd.PreRunner) *cobra.Command { + cliCmd := pcmd.NewAuthenticatedWithMDSStateFlagCommand( &cobra.Command{ - Use: "config", - Short: "Manage the audit log configuration specification.", - Long: "Manage the audit log defaults and routing rules that determine which auditable events are logged, and where.", + Use: "config", + Short: "Manage the audit log configuration specification.", + Long: "Manage the audit log defaults and routing rules that determine which auditable events are logged, and where.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, }, prerunner, ConfigSubcommandFlags) command := &configCommand{ AuthenticatedStateFlagCommand: cliCmd, @@ -42,7 +43,7 @@ func (c *configCommand) init() { Short: "Prints the audit log configuration spec object.", Long: `Prints the audit log configuration spec object, where "spec" refers to the JSON blob that describes audit log routing rules.`, Args: cobra.NoArgs, - RunE: cmd.NewCLIRunE(c.describe), + RunE: pcmd.NewCLIRunE(c.describe), } c.AddCommand(describeCmd) @@ -51,7 +52,7 @@ func (c *configCommand) init() { Short: "Submits audit-log config spec object to the API.", Long: "Submits an audit-log configuration specification JSON object to the API.", Args: cobra.NoArgs, - RunE: cmd.NewCLIRunE(c.update), + RunE: pcmd.NewCLIRunE(c.update), } updateCmd.Flags().String("file", "", "A local file path to the JSON configuration file, read as input. Otherwise the command will read from standard input.") updateCmd.Flags().Bool("force", false, "Updates the configuration, overwriting any concurrent modifications.") @@ -63,7 +64,7 @@ func (c *configCommand) init() { Short: "Edit the audit-log config spec interactively.", Long: "Edit the audit-log config spec object interactively, using the $EDITOR specified in your environment (for example, vim).", Args: cobra.NoArgs, - RunE: cmd.NewCLIRunE(c.edit), + RunE: pcmd.NewCLIRunE(c.edit), } c.AddCommand(editCmd) } diff --git a/internal/cmd/audit-log/command_describe.go b/internal/cmd/audit-log/command_describe.go index d51f37881c..9c7e9bb204 100644 --- a/internal/cmd/audit-log/command_describe.go +++ b/internal/cmd/audit-log/command_describe.go @@ -40,9 +40,10 @@ func NewDescribeCommand(prerunner pcmd.PreRunner) *cobra.Command { c := &describeCmd{ pcmd.NewAuthenticatedCLICommand( &cobra.Command{ - Use: "describe", - Short: "Describe the audit log configuration for this organization.", - Args: cobra.NoArgs, + Use: "describe", + Short: "Describe the audit log configuration for this organization.", + Args: cobra.NoArgs, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, }, prerunner, ), diff --git a/internal/cmd/audit-log/command_describe_test.go b/internal/cmd/audit-log/command_describe_test.go index fdd3e67211..6437fbace0 100644 --- a/internal/cmd/audit-log/command_describe_test.go +++ b/internal/cmd/audit-log/command_describe_test.go @@ -42,5 +42,5 @@ func mockAuditLogCommand(configured bool) *cobra.Command { } } - return New(cfg, climock.NewPreRunnerMock(client, nil, nil, cfg)) + return New(climock.NewPreRunnerMock(client, nil, nil, cfg)) } diff --git a/internal/cmd/audit-log/command_migrate.go b/internal/cmd/audit-log/command_migrate.go index 1a8c55e285..e367d8aef7 100644 --- a/internal/cmd/audit-log/command_migrate.go +++ b/internal/cmd/audit-log/command_migrate.go @@ -7,22 +7,23 @@ import ( "github.com/spf13/cobra" - "github.com/confluentinc/cli/internal/pkg/cmd" + pcmd "github.com/confluentinc/cli/internal/pkg/cmd" "github.com/confluentinc/cli/internal/pkg/errors" "github.com/confluentinc/cli/internal/pkg/examples" "github.com/confluentinc/cli/internal/pkg/utils" ) type migrateCmd struct { - *cmd.CLICommand - prerunner cmd.PreRunner + *pcmd.CLICommand + prerunner pcmd.PreRunner } -func NewMigrateCommand(prerunner cmd.PreRunner) *cobra.Command { - cliCmd := cmd.NewCLICommand( +func NewMigrateCommand(prerunner pcmd.PreRunner) *cobra.Command { + cliCmd := pcmd.NewCLICommand( &cobra.Command{ - Use: "migrate", - Short: "Migrate legacy audit log configurations.", + Use: "migrate", + Short: "Migrate legacy audit log configurations.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, }, prerunner) command := &migrateCmd{ CLICommand: cliCmd, diff --git a/internal/cmd/audit-log/command_route.go b/internal/cmd/audit-log/command_route.go index 969d47ae71..2f5f56e54a 100644 --- a/internal/cmd/audit-log/command_route.go +++ b/internal/cmd/audit-log/command_route.go @@ -8,21 +8,22 @@ import ( mds "github.com/confluentinc/mds-sdk-go/mdsv1" "github.com/spf13/cobra" - "github.com/confluentinc/cli/internal/pkg/cmd" + pcmd "github.com/confluentinc/cli/internal/pkg/cmd" ) type routeCommand struct { - *cmd.AuthenticatedStateFlagCommand - prerunner cmd.PreRunner + *pcmd.AuthenticatedStateFlagCommand + prerunner pcmd.PreRunner } // NewRouteCommand returns the sub-command object for interacting with audit log route rules. -func NewRouteCommand(prerunner cmd.PreRunner) *cobra.Command { - cliCmd := cmd.NewAuthenticatedWithMDSStateFlagCommand( +func NewRouteCommand(prerunner pcmd.PreRunner) *cobra.Command { + cliCmd := pcmd.NewAuthenticatedWithMDSStateFlagCommand( &cobra.Command{ - Use: "route", - Short: "Return the audit log route rules.", - Long: "Return the routing rules that determine which auditable events are logged, and where.", + Use: "route", + Short: "Return the audit log route rules.", + Long: "Return the routing rules that determine which auditable events are logged, and where.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, }, prerunner, RouteSubcommandFlags) command := &routeCommand{ AuthenticatedStateFlagCommand: cliCmd, @@ -38,7 +39,7 @@ func (c *routeCommand) init() { Short: "List routes matching a resource & sub-resources.", Long: "List the routes that match either the queried resource or its sub-resources.", Args: cobra.NoArgs, - RunE: cmd.NewCLIRunE(c.list), + RunE: pcmd.NewCLIRunE(c.list), } listCmd.Flags().StringP("resource", "r", "", "The Confluent resource name (CRN) that is the subject of the query.") check(listCmd.MarkFlagRequired("resource")) @@ -50,7 +51,7 @@ func (c *routeCommand) init() { Short: "Return the matching audit-log route rule.", Long: "Return the single route that describes how audit log messages using this CRN would be routed, with all defaults populated.", Args: cobra.ExactArgs(1), - RunE: cmd.NewCLIRunE(c.lookup), + RunE: pcmd.NewCLIRunE(c.lookup), } c.AddCommand(lookupCmd) } diff --git a/internal/cmd/audit-log/command_test.go b/internal/cmd/audit-log/command_test.go index 4f9663da1e..2b0b85371c 100644 --- a/internal/cmd/audit-log/command_test.go +++ b/internal/cmd/audit-log/command_test.go @@ -198,7 +198,7 @@ func (suite *AuditConfigTestSuite) newMockCmd(expect chan MockCall) *cobra.Comma } mdsClient := mds.NewAPIClient(mds.NewConfiguration()) mdsClient.AuditLogConfigurationApi = suite.mockApi - return New(suite.conf, cliMock.NewPreRunnerMock(nil, mdsClient, nil, suite.conf)) + return New(cliMock.NewPreRunnerMock(nil, mdsClient, nil, suite.conf)) } func TestAuditConfigTestSuite(t *testing.T) { diff --git a/internal/cmd/cluster/command.go b/internal/cmd/cluster/command.go index ed989b05a7..d3a8af1f30 100644 --- a/internal/cmd/cluster/command.go +++ b/internal/cmd/cluster/command.go @@ -16,8 +16,9 @@ type command struct { func New(prerunner pcmd.PreRunner, metaClient Metadata) *cobra.Command { cmd := &command{ StateFlagCommand: pcmd.NewAnonymousStateFlagCommand(&cobra.Command{ - Use: "cluster", - Short: "Retrieve metadata about Confluent Platform clusters.", + Use: "cluster", + Short: "Retrieve metadata about Confluent Platform clusters.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, }, prerunner, SubcommandFlags), prerunner: prerunner, metaClient: metaClient, diff --git a/internal/cmd/command.go b/internal/cmd/command.go index b8441c2e4f..5f7c5a9321 100644 --- a/internal/cmd/command.go +++ b/internal/cmd/command.go @@ -10,7 +10,7 @@ import ( "github.com/confluentinc/cli/internal/cmd/admin" apikey "github.com/confluentinc/cli/internal/cmd/api-key" - auditlog "github.com/confluentinc/cli/internal/cmd/audit-log" + "github.com/confluentinc/cli/internal/cmd/audit-log" cloudsignup "github.com/confluentinc/cli/internal/cmd/cloud-signup" "github.com/confluentinc/cli/internal/cmd/cluster" "github.com/confluentinc/cli/internal/cmd/completion" @@ -107,72 +107,52 @@ func NewConfluentCommand(cfg *v3.Config, isTest bool, ver *pversion.Version) *co Version: ver, } - command := &command{Command: cli, Analytics: analyticsClient, logger: logger} - var serverCompleter completer.ServerSideCompleter shellCompleter := completer.NewShellCompleter(cli) - if cfg.IsCloud() { + if cfg.IsCloudLogin() { serverCompleter = shellCompleter.ServerSideCompleter } - isAPIKeyLogin := isAPIKeyCredential(cfg) + apiKeyCmd := apikey.New(prerunner, nil, flagResolver, analyticsClient) + connectCmd := connect.New(cfg, prerunner, analyticsClient) + environmentCmd := environment.New(prerunner, analyticsClient) + serviceAccountCmd := serviceaccount.New(prerunner, analyticsClient) - // No-login commands + cli.AddCommand(admin.New(prerunner, isTest)) + cli.AddCommand(apiKeyCmd.Command) + cli.AddCommand(auditlog.New(prerunner)) + cli.AddCommand(cluster.New(prerunner, cluster.NewScopedIdService(ver.UserAgent, logger))) cli.AddCommand(cloudsignup.New(prerunner, logger, ver.UserAgent, ccloudClientFactory).Command) cli.AddCommand(completion.New(cli)) - cli.AddCommand(config.New(cfg.IsCloud(), prerunner, analyticsClient)) - cli.AddCommand(kafka.New(cfg, isAPIKeyLogin, prerunner, logger.Named("kafka"), ver.ClientID, serverCompleter, analyticsClient)) + cli.AddCommand(config.New(cfg.IsCloudLogin(), prerunner, analyticsClient)) + cli.AddCommand(connectCmd.Command) + cli.AddCommand(environmentCmd.Command) + cli.AddCommand(iam.New(cfg, prerunner)) + cli.AddCommand(initcontext.New(prerunner, flagResolver, analyticsClient)) + cli.AddCommand(kafka.New(cfg, isAPIKeyCredential(cfg), prerunner, logger.Named("kafka"), ver.ClientID, serverCompleter, analyticsClient)) + cli.AddCommand(ksql.New(cfg, prerunner, serverCompleter, analyticsClient)) cli.AddCommand(local.New(prerunner)) cli.AddCommand(login.New(prerunner, logger, ccloudClientFactory, mdsClientManager, analyticsClient, netrcHandler, loginCredentialsManager, authTokenHandler, isTest).Command) cli.AddCommand(logout.New(cfg, prerunner, analyticsClient, netrcHandler).Command) - cli.AddCommand(secret.New(flagResolver, secrets.NewPasswordProtectionPlugin(logger))) - if !cfg.DisableUpdates { - cli.AddCommand(update.New(logger, ver, updateClient, analyticsClient)) - } + cli.AddCommand(price.New(prerunner)) + cli.AddCommand(prompt.New(cfg, prerunner, &ps1.Prompt{}, logger)) + cli.AddCommand(schemaregistry.New(cfg, prerunner, nil, logger, analyticsClient)) + cli.AddCommand(secret.New(prerunner, flagResolver, secrets.NewPasswordProtectionPlugin(logger))) + cli.AddCommand(serviceAccountCmd.Command) + cli.AddCommand(shell.NewShellCmd(cli, prerunner, cfg, shellCompleter, jwtValidator)) + cli.AddCommand(update.New(prerunner, logger, ver, updateClient, analyticsClient)) cli.AddCommand(version.New(prerunner, ver)) - if cfg.IsCloud() { - cli.AddCommand(admin.New(prerunner, isTest)) - cli.AddCommand(auditlog.New(cfg, prerunner)) - cli.AddCommand(initcontext.New(prerunner, flagResolver, analyticsClient)) - - // If a user logs in with an API key, don't allow the remaining commands. - if isAPIKeyLogin { - return command - } - - apiKeyCmd := apikey.New(prerunner, nil, flagResolver, analyticsClient) - connectCmd := connect.New(cfg, prerunner, analyticsClient) - environmentCmd := environment.New(prerunner, analyticsClient) - serviceAccountCmd := serviceaccount.New(prerunner, analyticsClient) - + if cfg.IsCloudLogin() { serverCompleter.AddCommand(apiKeyCmd) serverCompleter.AddCommand(connectCmd) serverCompleter.AddCommand(environmentCmd) serverCompleter.AddCommand(serviceAccountCmd) - - cli.AddCommand(apiKeyCmd.Command) - cli.AddCommand(connectCmd.Command) - cli.AddCommand(environmentCmd.Command) - cli.AddCommand(iam.New(cfg, prerunner)) - cli.AddCommand(ksql.New(cfg, prerunner, serverCompleter, analyticsClient)) - cli.AddCommand(price.New(prerunner)) - cli.AddCommand(prompt.New(cfg, prerunner, &ps1.Prompt{}, logger)) - cli.AddCommand(schemaregistry.New(cfg, prerunner, nil, logger, analyticsClient)) - cli.AddCommand(serviceAccountCmd.Command) - cli.AddCommand(shell.NewShellCmd(cli, prerunner, cfg, shellCompleter, jwtValidator)) } - if cfg.IsOnPrem() { - cli.AddCommand(auditlog.New(cfg, prerunner)) - cli.AddCommand(cluster.New(prerunner, cluster.NewScopedIdService(ver.UserAgent, logger))) - cli.AddCommand(connect.New(cfg, prerunner, analyticsClient).Command) - cli.AddCommand(iam.New(cfg, prerunner)) - cli.AddCommand(ksql.New(cfg, prerunner, serverCompleter, analyticsClient)) - cli.AddCommand(schemaregistry.New(cfg, prerunner, nil, logger, analyticsClient)) - } + hideAndErrIfMissingRunRequirement(cli, cfg) - return command + return &command{Command: cli, Analytics: analyticsClient, logger: logger} } func getAnalyticsClient(isTest bool, cliName string, cfg *v3.Config, cliVersion string, logger *log.Logger) analytics.Client { @@ -219,13 +199,30 @@ func LoadConfig() (*v3.Config, error) { } func getLongDescription(cfg *v3.Config) string { - if cfg.IsCloud() { + switch { + case cfg.IsCloudLogin(): return "Manage your Confluent Cloud." + case cfg.IsOnPremLogin(): + return "Manage your Confluent Platform." + default: + return "Manage your Confluent Cloud or Confluent Platform. Log in to see all available commands." } +} - if cfg.IsOnPrem() { - return "Manage your Confluent Platform." +// hideAndErrIfMissingRunRequirement hides commands that don't meet a requirement and errs if a user attempts to use it; +// for example, an on-prem command shouldn't be used by a cloud user. +func hideAndErrIfMissingRunRequirement(cmd *cobra.Command, cfg *v3.Config) { + if err := pcmd.ErrIfMissingRunRequirement(cmd, cfg); err != nil { + cmd.Hidden = true + + // Show err for internal commands. Leaf commands will err in the PreRun function. + if cmd.HasSubCommands() { + cmd.RunE = func(_ *cobra.Command, _ []string) error { return err } + cmd.SilenceUsage = true + } } - return "Manage your Confluent Cloud or Confluent Platform. Log in to see all available commands." + for _, subcommand := range cmd.Commands() { + hideAndErrIfMissingRunRequirement(subcommand, cfg) + } } diff --git a/internal/cmd/command_test.go b/internal/cmd/command_test.go index ef9f2f4f4a..3aba958007 100644 --- a/internal/cmd/command_test.go +++ b/internal/cmd/command_test.go @@ -27,7 +27,7 @@ func TestHelp_NoContext(t *testing.T) { require.NoError(t, err) commands := []string{ - "completion", "config", "help", "kafka", "local", "login", "logout", "secret", "update", "version", + "completion", "config", "help", "kafka", "local", "login", "logout", "signup", "update", "version", } if runtime.GOOS == "windows" { commands = utils.Remove(commands, "local") @@ -50,8 +50,8 @@ func TestHelp_Cloud(t *testing.T) { commands := []string{ "admin", "api-key", "audit-log", "cloud-signup", "completion", "config", "connect", "environment", "help", - "iam", "init", "kafka", "ksql", "login", "logout", "price", "prompt", "schema-registry", "secret", - "service-account", "shell", "update", "version", + "iam", "init", "kafka", "ksql", "login", "logout", "price", "prompt", "schema-registry", "service-account", + "shell", "update", "version", } for _, command := range commands { @@ -75,7 +75,7 @@ func TestHelp_CloudWithAPIKey(t *testing.T) { require.NoError(t, err) commands := []string{ - "admin", "audit-log", "completion", "config", "help", "init", "kafka", "login", "logout", "secret", "update", + "admin", "audit-log", "completion", "config", "help", "init", "kafka", "login", "logout", "signup", "update", "version", } @@ -96,7 +96,7 @@ func TestHelp_OnPrem(t *testing.T) { commands := []string{ "audit-log", "cluster", "completion", "config", "connect", "help", "iam", "kafka", "ksql", "local", "login", - "logout", "schema-registry", "secret", "update", "version", + "logout", "schema-registry", "secret", "signup", "update", "version", } if runtime.GOOS == "windows" { commands = utils.Remove(commands, "local") diff --git a/internal/cmd/config/context.go b/internal/cmd/config/context.go index d7e33babeb..6fca067bbb 100644 --- a/internal/cmd/config/context.go +++ b/internal/cmd/config/context.go @@ -62,7 +62,7 @@ func (c *contextCommand) init() { RunE: pcmd.NewCLIRunE(c.use), PersistentPreRunE: func(cmd *cobra.Command, args []string) error { c.analytics.SetCommandType(analytics.ContextUse) - return c.prerunner.Anonymous(c.CLICommand)(cmd, args) + return c.prerunner.Anonymous(c.CLICommand, false)(cmd, args) }, }) currentCmd := &cobra.Command{ diff --git a/internal/cmd/connect/command.go b/internal/cmd/connect/command.go index a6424632f3..8e6329fedf 100644 --- a/internal/cmd/connect/command.go +++ b/internal/cmd/connect/command.go @@ -64,8 +64,9 @@ var ( func New(cfg *v3.Config, prerunner pcmd.PreRunner, analyticsClient analytics.Client) *command { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "connect", - Short: "Manage Kafka Connect.", + Use: "connect", + Short: "Manage Kafka Connect.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLoginOrOnPremLogin}, }, prerunner, SubcommandFlags) cmd := &command{ AuthenticatedStateFlagCommand: cliCmd, @@ -78,10 +79,11 @@ func New(cfg *v3.Config, prerunner pcmd.PreRunner, analyticsClient analytics.Cli func (c *command) init(cfg *v3.Config) { describeCmd := &cobra.Command{ - Use: "describe ", - Short: "Describe a connector.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.describe), + Use: "describe ", + Short: "Describe a connector.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.describe), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Describe connector and task level details of a connector in the current or specified Kafka cluster context.", @@ -93,10 +95,11 @@ func (c *command) init(cfg *v3.Config) { describeCmd.Flags().SortFlags = false listCmd := &cobra.Command{ - Use: "list", - Short: "List connectors.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.list), + Use: "list", + Short: "List connectors.", + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.list), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "List connectors in the current or specified Kafka cluster context.", @@ -108,10 +111,11 @@ func (c *command) init(cfg *v3.Config) { listCmd.Flags().SortFlags = false createCmd := &cobra.Command{ - Use: "create", - Short: "Create a connector.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.create), + Use: "create", + Short: "Create a connector.", + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.create), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Create a connector in the current or specified Kafka cluster context.", @@ -125,10 +129,11 @@ func (c *command) init(cfg *v3.Config) { createCmd.Flags().SortFlags = false deleteCmd := &cobra.Command{ - Use: "delete ", - Short: "Delete a connector.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.delete), + Use: "delete ", + Short: "Delete a connector.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.delete), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Delete a connector in the current or specified Kafka cluster context.", @@ -138,20 +143,22 @@ func (c *command) init(cfg *v3.Config) { } updateCmd := &cobra.Command{ - Use: "update ", - Short: "Update a connector configuration.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.update), + Use: "update ", + Short: "Update a connector configuration.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.update), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, } updateCmd.Flags().String("config", "", "JSON connector config file.") panicOnError(updateCmd.MarkFlagRequired("config")) updateCmd.Flags().SortFlags = false pauseCmd := &cobra.Command{ - Use: "pause ", - Short: "Pause a connector.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.pause), + Use: "pause ", + Short: "Pause a connector.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.pause), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Pause a connector in the current or specified Kafka cluster context.", @@ -161,10 +168,11 @@ func (c *command) init(cfg *v3.Config) { } resumeCmd := &cobra.Command{ - Use: "resume ", - Short: "Resume a connector.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.resume), + Use: "resume ", + Short: "Resume a connector.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.resume), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Resume a connector in the current or specified Kafka cluster context.", @@ -173,19 +181,17 @@ func (c *command) init(cfg *v3.Config) { ), } - if cfg.IsCloud() { - c.AddCommand(describeCmd) - c.AddCommand(listCmd) - c.AddCommand(createCmd) - c.AddCommand(deleteCmd) - c.AddCommand(updateCmd) - c.AddCommand(pauseCmd) - c.AddCommand(resumeCmd) - c.AddCommand(NewEventCommand(c.prerunner)) - c.AddCommand(NewPluginCommand(c.prerunner)) - } else { - c.AddCommand(NewClusterCommandOnPrem(c.prerunner)) - } + c.AddCommand(NewClusterCommandOnPrem(c.prerunner)) + c.AddCommand(createCmd) + c.AddCommand(deleteCmd) + c.AddCommand(describeCmd) + c.AddCommand(NewEventCommand(c.prerunner)) + c.AddCommand(listCmd) + c.AddCommand(pauseCmd) + c.AddCommand(NewPluginCommand(c.prerunner)) + c.AddCommand(resumeCmd) + c.AddCommand(updateCmd) + c.completableChildren = []*cobra.Command{deleteCmd, describeCmd, pauseCmd, resumeCmd, updateCmd} c.completableFlagChildren = map[string][]*cobra.Command{ "cluster": {createCmd}, diff --git a/internal/cmd/connect/command_cluster_onprem.go b/internal/cmd/connect/command_cluster_onprem.go index d2036fa232..04f48417fa 100644 --- a/internal/cmd/connect/command_cluster_onprem.go +++ b/internal/cmd/connect/command_cluster_onprem.go @@ -23,8 +23,9 @@ type clusterCommandOnPrem struct { func NewClusterCommandOnPrem(prerunner pcmd.PreRunner) *cobra.Command { cliCmd := pcmd.NewAuthenticatedWithMDSStateFlagCommand( &cobra.Command{ - Use: "cluster", - Short: "Manage Connect clusters.", + Use: "cluster", + Short: "Manage Connect clusters.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, }, prerunner, ClusterSubcommandFlags) cmd := &clusterCommandOnPrem{ diff --git a/internal/cmd/connect/command_event.go b/internal/cmd/connect/command_event.go index 17554422fb..13ca6f9e9d 100644 --- a/internal/cmd/connect/command_event.go +++ b/internal/cmd/connect/command_event.go @@ -40,8 +40,9 @@ func NewEventCommand(prerunner pcmd.PreRunner) *cobra.Command { cmd := &eventCommand{ pcmd.NewAuthenticatedCLICommand( &cobra.Command{ - Use: "event", - Short: "Manage Connect log events configuration.", + Use: "event", + Short: "Manage Connect log events configuration.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner, ), diff --git a/internal/cmd/connect/command_plugin.go b/internal/cmd/connect/command_plugin.go index 024333b9ed..e9d418ea19 100644 --- a/internal/cmd/connect/command_plugin.go +++ b/internal/cmd/connect/command_plugin.go @@ -35,8 +35,9 @@ var ( func NewPluginCommand(prerunner pcmd.PreRunner) *cobra.Command { c := &pluginCommand{ AuthenticatedStateFlagCommand: pcmd.NewAuthenticatedStateFlagCommand(&cobra.Command{ - Use: "plugin", - Short: "Show plugins and their configurations.", + Use: "plugin", + Short: "Show plugins and their configurations.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner, SubcommandFlags), } c.init() diff --git a/internal/cmd/environment/command.go b/internal/cmd/environment/command.go index 03be1b1a5f..134d358cbb 100644 --- a/internal/cmd/environment/command.go +++ b/internal/cmd/environment/command.go @@ -34,8 +34,9 @@ var ( func New(prerunner pcmd.PreRunner, analyticsClient analytics.Client) *command { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "environment", - Short: "Manage and select Confluent Cloud environments.", + Use: "environment", + Short: "Manage and select Confluent Cloud environments.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner, SubcommandFlags) cmd := &command{AuthenticatedStateFlagCommand: cliCmd, analyticsClient: analyticsClient} cmd.init() diff --git a/internal/cmd/iam/command.go b/internal/cmd/iam/command.go index b8251edfa2..7c0a89275d 100644 --- a/internal/cmd/iam/command.go +++ b/internal/cmd/iam/command.go @@ -14,21 +14,20 @@ type command struct { // New returns the default command object for interacting with RBAC. func New(cfg *v3.Config, prerunner pcmd.PreRunner) *cobra.Command { + cmd := &cobra.Command{ + Use: "iam", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLoginOrOnPremLogin}, + } + var cliCmd *pcmd.AuthenticatedCLICommand - if cfg.IsOnPrem() { - cliCmd = pcmd.NewAuthenticatedWithMDSCLICommand( - &cobra.Command{ - Use: "iam", - Short: "Manage RBAC, ACL and IAM permissions.", - Long: "Manage Role-Based Access Control (RBAC), Access Control Lists (ACL), and Identity and Access Management (IAM) permissions.", - }, prerunner) + if cfg.IsOnPremLogin() { + cmd.Short = "Manage RBAC, ACL and IAM permissions." + cmd.Long = "Manage Role-Based Access Control (RBAC), Access Control Lists (ACL), and Identity and Access Management (IAM) permissions." + cliCmd = pcmd.NewAuthenticatedWithMDSCLICommand(cmd, prerunner) } else { - cliCmd = pcmd.NewAuthenticatedCLICommand( - &cobra.Command{ - Use: "iam", - Short: "Manage RBAC and IAM permissions.", - Long: "Manage Role-Based Access Control (RBAC) and Identity and Access Management (IAM) permissions.", - }, prerunner) + cmd.Short = "Manage RBAC and IAM permissions." + cmd.Long = "Manage Role-Based Access Control (RBAC) and Identity and Access Management (IAM) permissions." + cliCmd = pcmd.NewAuthenticatedCLICommand(cmd, prerunner) } c := &command{ @@ -36,11 +35,9 @@ func New(cfg *v3.Config, prerunner pcmd.PreRunner) *cobra.Command { prerunner: prerunner, } + c.AddCommand(NewACLCommand(c.prerunner)) c.AddCommand(NewRoleCommand(cfg, c.prerunner)) c.AddCommand(NewRolebindingCommand(cfg, c.prerunner)) - if cfg.IsOnPrem() { - c.AddCommand(NewACLCommand(c.prerunner)) - } return c.Command } diff --git a/internal/cmd/iam/command_acl.go b/internal/cmd/iam/command_acl.go index 66cb3eb207..e74fec59f2 100644 --- a/internal/cmd/iam/command_acl.go +++ b/internal/cmd/iam/command_acl.go @@ -36,11 +36,12 @@ func NewACLCommand(prerunner pcmd.PreRunner) *cobra.Command { func (c *aclCommand) init() { cmd := &cobra.Command{ - Use: "create", - Short: "Create a Kafka ACL.", - Long: "Create a Kafka ACL.\n\nThis command only works with centralized ACLs.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.create), + Use: "create", + Short: "Create a Kafka ACL.", + Long: "Create a Kafka ACL.\n\nThis command only works with centralized ACLs.", + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.create), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Create an ACL that grants the specified user READ permission to the specified consumer group in the specified Kafka cluster:", diff --git a/internal/cmd/iam/command_role.go b/internal/cmd/iam/command_role.go index d978bd1a43..aea3334e05 100644 --- a/internal/cmd/iam/command_role.go +++ b/internal/cmd/iam/command_role.go @@ -45,7 +45,7 @@ func NewRoleCommand(cfg *v3.Config, prerunner cmd.PreRunner) *cobra.Command { Long: "Manage Role-Based Access Control (RBAC) and Identity and Access Management (IAM) roles.", } var cliCmd *cmd.AuthenticatedStateFlagCommand - if cfg.IsOnPrem() { + if cfg.IsOnPremLogin() { cliCmd = cmd.NewAuthenticatedWithMDSStateFlagCommand(cobraRoleCmd, prerunner, RoleSubcommandFlags) } else { cliCmd = cmd.NewAuthenticatedStateFlagCommand(cobraRoleCmd, prerunner, nil) @@ -59,7 +59,7 @@ func NewRoleCommand(cfg *v3.Config, prerunner cmd.PreRunner) *cobra.Command { } func (c *roleCommand) createContext() context.Context { - if c.cfg.IsCloud() { + if c.cfg.IsCloudLogin() { return context.WithValue(context.Background(), mdsv2alpha1.ContextAccessToken, c.State.AuthToken) } else { return context.WithValue(context.Background(), mds.ContextAccessToken, c.State.AuthToken) @@ -140,7 +140,7 @@ func (c *roleCommand) ccloudList(cmd *cobra.Command) error { } func (c *roleCommand) list(cmd *cobra.Command, _ []string) error { - if c.cfg.IsCloud() { + if c.cfg.IsCloudLogin() { return c.ccloudList(cmd) } else { return c.confluentList(cmd) @@ -221,7 +221,7 @@ func (c *roleCommand) ccloudDescribe(cmd *cobra.Command, role string) error { func (c *roleCommand) describe(cmd *cobra.Command, args []string) error { role := args[0] - if c.cfg.IsCloud() { + if c.cfg.IsCloudLogin() { return c.ccloudDescribe(cmd, role) } else { return c.confluentDescribe(cmd, role) diff --git a/internal/cmd/iam/command_rolebinding.go b/internal/cmd/iam/command_rolebinding.go index b29333d232..28b4fc585c 100644 --- a/internal/cmd/iam/command_rolebinding.go +++ b/internal/cmd/iam/command_rolebinding.go @@ -88,7 +88,7 @@ func NewRolebindingCommand(cfg *v3.Config, prerunner cmd.PreRunner) *cobra.Comma Long: "Manage Role-Based Access Control (RBAC) and Identity and Access Management (IAM) role bindings.", } var cliCmd *cmd.AuthenticatedStateFlagCommand - if cfg.IsOnPrem() { + if cfg.IsOnPremLogin() { cliCmd = cmd.NewAuthenticatedWithMDSStateFlagCommand(cobraRolebindingCmd, prerunner, RolebindingSubcommandFlags) } else { cliCmd = cmd.NewAuthenticatedStateFlagCommand(cobraRolebindingCmd, prerunner, nil) @@ -102,7 +102,7 @@ func NewRolebindingCommand(cfg *v3.Config, prerunner cmd.PreRunner) *cobra.Comma } func (c *rolebindingCommand) init() { - isCloud := c.cfg.IsCloud() + isCloud := c.cfg.IsCloudLogin() var example string if isCloud { @@ -524,7 +524,7 @@ func (c *rolebindingCommand) list(cmd *cobra.Command, _ []string) error { if err != nil { return err } - if c.cfg.IsCloud() { + if c.cfg.IsCloudLogin() { return c.ccloudList(cmd, options) } else { return c.confluentList(cmd, options) @@ -731,7 +731,7 @@ func (c *rolebindingCommand) parseCommon(cmd *cobra.Command) (*rolebindingOption return nil, err } - isCloud := c.cfg.IsCloud() + isCloud := c.cfg.IsCloudLogin() resource := "" prefix := false @@ -839,7 +839,7 @@ func (c *rolebindingCommand) create(cmd *cobra.Command, _ []string) error { return err } - isCloud := c.cfg.IsCloud() + isCloud := c.cfg.IsCloudLogin() var resp *http.Response if isCloud { @@ -934,7 +934,7 @@ func (c *rolebindingCommand) delete(cmd *cobra.Command, _ []string) error { return err } - isCloud := c.cfg.IsCloud() + isCloud := c.cfg.IsCloudLogin() var resp *http.Response if isCloud { @@ -965,7 +965,7 @@ func check(err error) { } func (c *rolebindingCommand) createContext() context.Context { - if c.cfg.IsCloud() { + if c.cfg.IsCloudLogin() { return context.WithValue(context.Background(), mdsv2alpha1.ContextAccessToken, c.AuthToken()) } else { return context.WithValue(context.Background(), mds.ContextAccessToken, c.AuthToken()) diff --git a/internal/cmd/init/command.go b/internal/cmd/init/command.go index 905ead3b94..2f23904f8b 100644 --- a/internal/cmd/init/command.go +++ b/internal/cmd/init/command.go @@ -36,11 +36,12 @@ func New(prerunner pcmd.PreRunner, resolver pcmd.FlagResolver, analyticsClient a Code: `confluent init "new context" --kafka-auth`, }, ), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, } cliCmd := pcmd.NewAnonymousCLICommand(cobraCmd, prerunner) cobraCmd.PersistentPreRunE = pcmd.NewCLIPreRunnerE(func(cmd *cobra.Command, args []string) error { analyticsClient.SetCommandType(analytics.Init) - return prerunner.Anonymous(cliCmd)(cmd, args) + return prerunner.Anonymous(cliCmd, false)(cmd, args) }) cmd := &command{ CLICommand: cliCmd, diff --git a/internal/cmd/kafka/command.go b/internal/cmd/kafka/command.go index eef3d99c75..88bc3f2e0b 100644 --- a/internal/cmd/kafka/command.go +++ b/internal/cmd/kafka/command.go @@ -40,41 +40,24 @@ func New(cfg *v3.Config, isAPIKeyLogin bool, prerunner pcmd.PreRunner, logger *l } func (c *command) init(cfg *v3.Config, isAPIKeyLogin bool) { - if cfg.IsCloud() { - topicCmd := NewTopicCommand(isAPIKeyLogin, c.prerunner, c.logger, c.clientID) - // Order matters here. If we add to the server-side completer first then the command doesn't have a parent - // and that doesn't trigger completion. - c.AddCommand(topicCmd.hasAPIKeyTopicCommand.Command) - c.serverCompleter.AddCommand(topicCmd) - - if isAPIKeyLogin { - return - } - - aclCmd := NewACLCommand(c.prerunner) - clusterCmd := NewClusterCommand(c.prerunner, c.analyticsClient) - groupCmd := NewGroupCommand(c.prerunner, c.serverCompleter) - - c.AddCommand(aclCmd.Command) - c.AddCommand(clusterCmd.Command) - c.AddCommand(groupCmd.Command) - c.AddCommand(NewLinkCommand(c.prerunner)) - c.AddCommand(NewMirrorCommand(c.prerunner)) - c.AddCommand(NewRegionCommand(c.prerunner)) - + aclCmd := NewACLCommand(cfg, c.prerunner) + clusterCmd := NewClusterCommand(cfg, c.prerunner, c.analyticsClient) + groupCmd := NewGroupCommand(c.prerunner, c.serverCompleter) + topicCmd := NewTopicCommand(cfg, isAPIKeyLogin, c.prerunner, c.logger, c.clientID) + + c.AddCommand(NewLinkCommand(c.prerunner)) + c.AddCommand(NewMirrorCommand(c.prerunner)) + c.AddCommand(NewRegionCommand(c.prerunner)) + c.AddCommand(aclCmd.Command) + c.AddCommand(clusterCmd.Command) + c.AddCommand(groupCmd.Command) + c.AddCommand(topicCmd.hasAPIKeyTopicCommand.Command) + + if cfg.IsCloudLogin() { c.serverCompleter.AddCommand(aclCmd) c.serverCompleter.AddCommand(clusterCmd) c.serverCompleter.AddCommand(groupCmd) c.serverCompleter.AddCommand(groupCmd.lagCmd) - - return - } - - // These on-prem commands can also be run without logging in. - c.AddCommand(NewAclCommandOnPrem(c.prerunner)) - c.AddCommand(NewTopicCommandOnPrem(c.prerunner)) - - if cfg.IsOnPrem() { - c.AddCommand(NewClusterCommandOnPrem(c.prerunner)) + c.serverCompleter.AddCommand(topicCmd) } } diff --git a/internal/cmd/kafka/command_acl.go b/internal/cmd/kafka/command_acl.go index e3a2687c50..28fbb97645 100644 --- a/internal/cmd/kafka/command_acl.go +++ b/internal/cmd/kafka/command_acl.go @@ -16,6 +16,7 @@ import ( aclutil "github.com/confluentinc/cli/internal/pkg/acl" pcmd "github.com/confluentinc/cli/internal/pkg/cmd" + v3 "github.com/confluentinc/cli/internal/pkg/config/v3" "github.com/confluentinc/cli/internal/pkg/errors" "github.com/confluentinc/cli/internal/pkg/examples" "github.com/confluentinc/cli/internal/pkg/output" @@ -35,15 +36,27 @@ type aclCommand struct { } // NewACLCommand returns the Cobra command for Kafka ACL. -func NewACLCommand(prerunner pcmd.PreRunner) *aclCommand { - cliCmd := pcmd.NewAuthenticatedStateFlagCommand( - &cobra.Command{ - Use: "acl", - Short: "Manage Kafka ACLs.", - }, prerunner, AclSubcommandFlags) - cmd := &aclCommand{AuthenticatedStateFlagCommand: cliCmd} - cmd.init() - return cmd +func NewACLCommand(cfg *v3.Config, prerunner pcmd.PreRunner) *aclCommand { + cmd := &cobra.Command{ + Use: "acl", + Short: "Manage Kafka ACLs.", + } + + flagMap := OnPremTopicSubcommandFlags + if cfg.IsCloudLogin() { + flagMap = AclSubcommandFlags + } + + c := &aclCommand{AuthenticatedStateFlagCommand: pcmd.NewAuthenticatedStateFlagCommand(cmd, prerunner, flagMap)} + + if cfg.IsCloudLogin() { + c.init() + } else { + c.SetPersistentPreRunE(prerunner.InitializeOnPremKafkaRest(c.AuthenticatedCLICommand)) + c.onPremInit() + } + + return c } func (c *aclCommand) init() { diff --git a/internal/cmd/kafka/command_acl_onprem.go b/internal/cmd/kafka/command_acl_onprem.go index 8ef67b91ca..c52bf03687 100644 --- a/internal/cmd/kafka/command_acl_onprem.go +++ b/internal/cmd/kafka/command_acl_onprem.go @@ -15,29 +15,12 @@ var ( onPremAclListStructuredRenames = []string{"principal", "permission", "operation", "host", "resource_type", "resource_name", "pattern_type"} ) -type aclOnPremCommand struct { - *pcmd.AuthenticatedStateFlagCommand -} - -func NewAclCommandOnPrem(prerunner pcmd.PreRunner) *cobra.Command { - aclCmd := &aclOnPremCommand{ - pcmd.NewAuthenticatedStateFlagCommand( - &cobra.Command{ - Use: "acl", - Short: "Manage Kafka ACLs.", - }, prerunner, OnPremTopicSubcommandFlags), - } - aclCmd.SetPersistentPreRunE(prerunner.InitializeOnPremKafkaRest(aclCmd.AuthenticatedCLICommand)) - aclCmd.init() - return aclCmd.Command -} - -func (aclCmd *aclOnPremCommand) init() { +func (c *aclCommand) onPremInit() { createCmd = &cobra.Command{ Use: "create", Short: "Create a Kafka ACL.", Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(aclCmd.create), + RunE: pcmd.NewCLIRunE(c.onPremCreate), Example: examples.BuildExampleString( examples.Example{ Text: "You can specify only one of the following flags per command invocation: `cluster-scope`, `consumer-group`, `topic`, or `transactional-id`. For example, for a consumer to read a topic, you need to grant `READ` and `DESCRIBE` both on the `consumer-group` and the `topic` resources, issuing two separate commands:", @@ -48,13 +31,13 @@ func (aclCmd *aclOnPremCommand) init() { createCmd.Flags().AddFlagSet(aclutil.CreateACLFlags()) createCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) createCmd.Flags().SortFlags = false - aclCmd.AddCommand(createCmd) + c.AddCommand(createCmd) deleteCmd = &cobra.Command{ Use: "delete", Short: "Delete Kafka ACLs matching the search criteria.", Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(aclCmd.delete), + RunE: pcmd.NewCLIRunE(c.onPremDelete), Example: examples.BuildExampleString( examples.Example{ Text: "Delete all READ access ACLs for the specified user:", @@ -65,13 +48,13 @@ func (aclCmd *aclOnPremCommand) init() { deleteCmd.Flags().AddFlagSet(aclutil.DeleteACLFlags()) deleteCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) deleteCmd.Flags().SortFlags = false - aclCmd.AddCommand(deleteCmd) + c.AddCommand(deleteCmd) listCmd = &cobra.Command{ Use: "list", Short: "List Kafka ACLs.", Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(aclCmd.list), + RunE: pcmd.NewCLIRunE(c.onPremList), Example: examples.BuildExampleString( examples.Example{ Text: "List all the local ACLs for the Kafka cluster:", @@ -86,15 +69,15 @@ func (aclCmd *aclOnPremCommand) init() { listCmd.Flags().AddFlagSet(pcmd.OnPremKafkaRestSet()) listCmd.Flags().AddFlagSet(aclutil.AclFlags()) listCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) - aclCmd.AddCommand(listCmd) + c.AddCommand(listCmd) } -func (aclCmd *aclOnPremCommand) list(cmd *cobra.Command, _ []string) error { +func (c *aclCommand) onPremList(cmd *cobra.Command, _ []string) error { acl := aclutil.ParseAclRequest(cmd) if acl.Errors != nil { return acl.Errors } - restClient, restContext, err := initKafkaRest(aclCmd.AuthenticatedCLICommand, cmd) + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } @@ -110,13 +93,13 @@ func (aclCmd *aclOnPremCommand) list(cmd *cobra.Command, _ []string) error { return aclutil.PrintACLsFromKafkaRestResponse(cmd, aclGetResp.Data, cmd.OutOrStdout(), onPremAclListFields, onPremAclListStructuredRenames) } -func (aclCmd *aclOnPremCommand) create(cmd *cobra.Command, _ []string) error { +func (c *aclCommand) onPremCreate(cmd *cobra.Command, _ []string) error { acl := aclutil.ParseAclRequest(cmd) acl = aclutil.ValidateCreateDeleteAclRequestData(acl) if acl.Errors != nil { return acl.Errors } - restClient, restContext, err := initKafkaRest(aclCmd.AuthenticatedCLICommand, cmd) + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } @@ -133,13 +116,13 @@ func (aclCmd *aclOnPremCommand) create(cmd *cobra.Command, _ []string) error { return aclutil.PrintACLsFromKafkaRestResponse(cmd, []kafkarestv3.AclData{aclData}, cmd.OutOrStdout(), onPremAclListFields, onPremAclListStructuredRenames) } -func (aclCmd *aclOnPremCommand) delete(cmd *cobra.Command, _ []string) error { +func (c *aclCommand) onPremDelete(cmd *cobra.Command, _ []string) error { acl := aclutil.ParseAclRequest(cmd) acl = aclutil.ValidateCreateDeleteAclRequestData(acl) if acl.Errors != nil { return acl.Errors } - restClient, restContext, err := initKafkaRest(aclCmd.AuthenticatedCLICommand, cmd) + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } diff --git a/internal/cmd/kafka/command_cluster_cloud.go b/internal/cmd/kafka/command_cluster.go similarity index 90% rename from internal/cmd/kafka/command_cluster_cloud.go rename to internal/cmd/kafka/command_cluster.go index aff293fba6..93c68e1250 100644 --- a/internal/cmd/kafka/command_cluster_cloud.go +++ b/internal/cmd/kafka/command_cluster.go @@ -17,6 +17,7 @@ import ( "github.com/confluentinc/cli/internal/pkg/analytics" pcmd "github.com/confluentinc/cli/internal/pkg/cmd" + v3 "github.com/confluentinc/cli/internal/pkg/config/v3" "github.com/confluentinc/cli/internal/pkg/errors" "github.com/confluentinc/cli/internal/pkg/examples" "github.com/confluentinc/cli/internal/pkg/form" @@ -94,29 +95,42 @@ type describeStruct struct { } // NewClusterCommand returns the command for Kafka cluster. -func NewClusterCommand(prerunner pcmd.PreRunner, analyticsClient analytics.Client) *clusterCommand { - cliCmd := pcmd.NewAuthenticatedStateFlagCommand( - &cobra.Command{ - Use: "cluster", - Short: "Manage Kafka clusters.", - }, prerunner, ClusterSubcommandFlags) - cmd := &clusterCommand{ - AuthenticatedStateFlagCommand: cliCmd, - prerunner: prerunner, - analyticsClient: analyticsClient, - } - cmd.init() - return cmd +func NewClusterCommand(cfg *v3.Config, prerunner pcmd.PreRunner, analyticsClient analytics.Client) *clusterCommand { + cmd := &cobra.Command{ + Use: "cluster", + Short: "Manage Kafka clusters.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLoginOrOnPremLogin}, + } + + c := &clusterCommand{ + prerunner: prerunner, + analyticsClient: analyticsClient, + } + if cfg.IsCloudLogin() { + c.AuthenticatedStateFlagCommand = pcmd.NewAuthenticatedStateFlagCommand(cmd, prerunner, ClusterSubcommandFlags) + } else { + c.AuthenticatedStateFlagCommand = pcmd.NewAuthenticatedWithMDSStateFlagCommand(cmd, prerunner, OnPremClusterSubcommandFlags) + } + + c.init(cfg) + + return c } -func (c *clusterCommand) init() { +func (c *clusterCommand) init(cfg *v3.Config) { listCmd := &cobra.Command{ - Use: "list", - Short: "List Kafka clusters.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.list), + Use: "list", + Args: cobra.NoArgs, + } + if cfg.IsCloudLogin() { + listCmd.Short = "List Kafka clusters." + listCmd.RunE = pcmd.NewCLIRunE(c.list) + listCmd.Flags().Bool("all", false, "List clusters across all environments.") + } else { + listCmd.Short = "List registered Kafka clusters." + listCmd.Long = "List Kafka clusters that are registered with the MDS cluster registry." + listCmd.RunE = pcmd.NewCLIRunE(c.onPremList) } - listCmd.Flags().Bool("all", false, "List clusters across all environments.") listCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) listCmd.Flags().SortFlags = false c.AddCommand(listCmd) @@ -129,6 +143,7 @@ func (c *clusterCommand) init() { RunE: pcmd.NewCLIRunE(func(cmd *cobra.Command, args []string) error { return c.create(cmd, args, form.NewPrompt(os.Stdin)) }), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Create a new dedicated cluster that uses a customer-managed encryption key in AWS:", @@ -153,20 +168,22 @@ func (c *clusterCommand) init() { c.AddCommand(createCmd) describeCmd := &cobra.Command{ - Use: "describe ", - Short: "Describe a Kafka cluster.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.describe), + Use: "describe ", + Short: "Describe a Kafka cluster.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.describe), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, } describeCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) describeCmd.Flags().SortFlags = false c.AddCommand(describeCmd) updateCmd := &cobra.Command{ - Use: "update ", - Short: "Update a Kafka cluster.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.update), + Use: "update ", + Short: "Update a Kafka cluster.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.update), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Change a cluster's name and expand its CKU count:", @@ -181,18 +198,20 @@ func (c *clusterCommand) init() { c.AddCommand(updateCmd) deleteCmd := &cobra.Command{ - Use: "delete ", - Short: "Delete a Kafka cluster.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.delete), + Use: "delete ", + Short: "Delete a Kafka cluster.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.delete), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, } c.AddCommand(deleteCmd) useCmd := &cobra.Command{ - Use: "use ", - Short: "Make the Kafka cluster active for use in other commands.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(c.use), + Use: "use ", + Short: "Make the Kafka cluster active for use in other commands.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(c.use), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, } c.AddCommand(useCmd) c.completableChildren = []*cobra.Command{deleteCmd, describeCmd, updateCmd, useCmd} diff --git a/internal/cmd/kafka/command_cluster_onprem.go b/internal/cmd/kafka/command_cluster_onprem.go index 76225c9485..fa02ff931a 100644 --- a/internal/cmd/kafka/command_cluster_onprem.go +++ b/internal/cmd/kafka/command_cluster_onprem.go @@ -8,51 +8,16 @@ import ( "github.com/spf13/cobra" print "github.com/confluentinc/cli/internal/pkg/cluster" - pcmd "github.com/confluentinc/cli/internal/pkg/cmd" "github.com/confluentinc/cli/internal/pkg/output" ) var kafkaClusterTypeName = "kafka-cluster" -type clusterCommandOnPrem struct { - *pcmd.AuthenticatedStateFlagCommand - prerunner pcmd.PreRunner -} - -// NewClusterCommand returns the Cobra command for Kafka cluster. -func NewClusterCommandOnPrem(prerunner pcmd.PreRunner) *cobra.Command { - cliCmd := pcmd.NewAuthenticatedWithMDSStateFlagCommand( - &cobra.Command{ - Use: "cluster", - Short: "Manage Kafka clusters.", - }, - prerunner, OnPremClusterSubcommandFlags) - cmd := &clusterCommandOnPrem{ - AuthenticatedStateFlagCommand: cliCmd, - prerunner: prerunner, - } - cmd.init() - return cmd.Command -} - -func (c *clusterCommandOnPrem) init() { - listCmd := &cobra.Command{ - Use: "list", - Short: "List registered Kafka clusters.", - Long: "List Kafka clusters that are registered with the MDS cluster registry.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.list), - } - listCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) - listCmd.Flags().SortFlags = false - c.AddCommand(listCmd) -} - -func (c *clusterCommandOnPrem) createContext() context.Context { +func (c *clusterCommand) createContext() context.Context { return context.WithValue(context.Background(), mds.ContextAccessToken, c.State.AuthToken) } -func (c *clusterCommandOnPrem) list(cmd *cobra.Command, _ []string) error { +func (c *clusterCommand) onPremList(cmd *cobra.Command, _ []string) error { clustertype := &mds.ClusterRegistryListOpts{ ClusterType: optional.NewString(kafkaClusterTypeName), } diff --git a/internal/cmd/kafka/command_cluster_cloud_test.go b/internal/cmd/kafka/command_cluster_test.go similarity index 99% rename from internal/cmd/kafka/command_cluster_cloud_test.go rename to internal/cmd/kafka/command_cluster_test.go index 0939746641..4ab0c232a9 100644 --- a/internal/cmd/kafka/command_cluster_cloud_test.go +++ b/internal/cmd/kafka/command_cluster_test.go @@ -17,6 +17,7 @@ import ( schedv1 "github.com/confluentinc/cc-structs/kafka/scheduler/v1" "github.com/confluentinc/ccloud-sdk-go-v1" ccsdkmock "github.com/confluentinc/ccloud-sdk-go-v1/mock" + "github.com/confluentinc/cli/internal/cmd/utils" "github.com/confluentinc/cli/internal/pkg/analytics" configv1 "github.com/confluentinc/cli/internal/pkg/config/v1" @@ -90,7 +91,7 @@ func (suite *KafkaClusterTestSuite) newCmd(conf *v3.Config) *clusterCommand { EnvironmentMetadata: suite.envMetadataMock, } prerunner := cliMock.NewPreRunnerMock(client, nil, nil, conf) - cmd := NewClusterCommand(prerunner, suite.analyticsClient) + cmd := NewClusterCommand(conf, prerunner, suite.analyticsClient) return cmd } diff --git a/internal/cmd/kafka/command_link.go b/internal/cmd/kafka/command_link.go index 72300ae3c7..cc86a71cf4 100644 --- a/internal/cmd/kafka/command_link.go +++ b/internal/cmd/kafka/command_link.go @@ -66,8 +66,10 @@ type linkCommand struct { func NewLinkCommand(prerunner pcmd.PreRunner) *cobra.Command { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "link", - Short: "Manages inter-cluster links.", + Use: "link", + Short: "Manages inter-cluster links.", + Hidden: true, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner, LinkSubcommandFlags) cmd := &linkCommand{ @@ -88,8 +90,8 @@ func (c *linkCommand) init() { Code: "ccloud kafka link list", }, ), - RunE: c.list, - Args: cobra.NoArgs, + RunE: c.list, + Args: cobra.NoArgs, } listCmd.Flags().Bool(includeTopicsFlagName, false, "If set, will list mirrored topics for the links returned.") listCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) @@ -109,8 +111,8 @@ func (c *linkCommand) init() { "--source-bootstrap-server myhost:1234 --source-api-key abcde --source-api-secret 88888 \n", }, ), - RunE: c.create, - Args: cobra.ExactArgs(1), + RunE: c.create, + Args: cobra.ExactArgs(1), } createCmd.Flags().String(sourceBootstrapServersFlagName, "", "Bootstrap-server address of the source cluster.") createCmd.Flags().String(sourceClusterIdFlagName, "", "Source cluster ID.") @@ -142,8 +144,8 @@ func (c *linkCommand) init() { Code: "ccloud kafka link delete my_link", }, ), - RunE: c.delete, - Args: cobra.ExactArgs(1), + RunE: c.delete, + Args: cobra.ExactArgs(1), } c.AddCommand(deleteCmd) @@ -156,8 +158,8 @@ func (c *linkCommand) init() { Code: "ccloud kafka link describe my_link", }, ), - RunE: c.describe, - Args: cobra.ExactArgs(1), + RunE: c.describe, + Args: cobra.ExactArgs(1), } describeCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) describeCmd.Flags().SortFlags = false @@ -173,8 +175,8 @@ func (c *linkCommand) init() { Code: "ccloud kafka link update my_link --config-file ~/config.txt", }, ), - RunE: c.update, - Args: cobra.ExactArgs(1), + RunE: c.update, + Args: cobra.ExactArgs(1), } updateCmd.Flags().String(configFileFlagName, "", "Name of the file containing link config overrides. "+ "Each property key-value pair should have the format of key=value. Properties are separated by new-line characters.") diff --git a/internal/cmd/kafka/command_mirror.go b/internal/cmd/kafka/command_mirror.go index fb64c8b64d..a4fde2a8e7 100644 --- a/internal/cmd/kafka/command_mirror.go +++ b/internal/cmd/kafka/command_mirror.go @@ -70,8 +70,10 @@ type mirrorCommand struct { func NewMirrorCommand(prerunner pcmd.PreRunner) *cobra.Command { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "mirror", - Short: "Manages cluster linking mirror topics.", + Use: "mirror", + Short: "Manages cluster linking mirror topics.", + Hidden: true, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner, MirrorSubcommandFlags) cmd := &mirrorCommand{ @@ -92,8 +94,8 @@ func (c *mirrorCommand) init() { Code: "ccloud kafka mirror list --link --mirror-status ", }, ), - RunE: c.list, - Args: cobra.NoArgs, + RunE: c.list, + Args: cobra.NoArgs, } listCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) listCmd.Flags().String(linkFlagName, "", "Cluster link name. If not specified, list all mirror topics in the cluster.") @@ -111,8 +113,8 @@ func (c *mirrorCommand) init() { Code: "ccloud kafka mirror describe --link ", }, ), - RunE: c.describe, - Args: cobra.ExactArgs(1), + RunE: c.describe, + Args: cobra.ExactArgs(1), } describeCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) describeCmd.Flags().String(linkFlagName, "", "Cluster link name.") @@ -130,8 +132,8 @@ func (c *mirrorCommand) init() { "--replication-factor --config-file mirror_config.txt", }, ), - RunE: c.create, - Args: cobra.ExactArgs(1), + RunE: c.create, + Args: cobra.ExactArgs(1), } createCmd.Flags().String(linkFlagName, "", "The name of the cluster link.") check(createCmd.MarkFlagRequired(linkFlagName)) @@ -150,8 +152,8 @@ func (c *mirrorCommand) init() { Code: "ccloud kafka mirror promote ... --link ", }, ), - RunE: c.promote, - Args: cobra.MinimumNArgs(1), + RunE: c.promote, + Args: cobra.MinimumNArgs(1), } promoteCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) promoteCmd.Flags().String(linkFlagName, "", "The name of the cluster link.") @@ -168,8 +170,8 @@ func (c *mirrorCommand) init() { Code: "ccloud kafka mirror failover ... --link ", }, ), - RunE: c.failover, - Args: cobra.MinimumNArgs(1), + RunE: c.failover, + Args: cobra.MinimumNArgs(1), } failoverCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) failoverCmd.Flags().String(linkFlagName, "", "The name of the cluster link.") @@ -186,8 +188,8 @@ func (c *mirrorCommand) init() { Code: "ccloud kafka mirror pause ... --link ", }, ), - RunE: c.pause, - Args: cobra.MinimumNArgs(1), + RunE: c.pause, + Args: cobra.MinimumNArgs(1), } pauseCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) pauseCmd.Flags().String(linkFlagName, "", "The name of the cluster link.") @@ -204,8 +206,8 @@ func (c *mirrorCommand) init() { Code: "ccloud kafka mirror resume ... ", }, ), - RunE: c.resume, - Args: cobra.MinimumNArgs(1), + RunE: c.resume, + Args: cobra.MinimumNArgs(1), } resumeCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) resumeCmd.Flags().String(linkFlagName, "", "The name of the cluster link.") diff --git a/internal/cmd/kafka/command_region.go b/internal/cmd/kafka/command_region.go index 8f1b89e580..a4cc92eb9a 100644 --- a/internal/cmd/kafka/command_region.go +++ b/internal/cmd/kafka/command_region.go @@ -23,9 +23,10 @@ type regionCommand struct { func NewRegionCommand(prerunner pcmd.PreRunner) *cobra.Command { cliCmd := pcmd.NewAuthenticatedCLICommand( &cobra.Command{ - Use: "region", - Short: "Manage Confluent Cloud regions.", - Long: "Use this command to manage Confluent Cloud regions.", + Use: "region", + Short: "Manage Confluent Cloud regions.", + Long: "Use this command to manage Confluent Cloud regions.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner) cmd := ®ionCommand{ AuthenticatedCLICommand: cliCmd, diff --git a/internal/cmd/kafka/command_topic.go b/internal/cmd/kafka/command_topic.go index 53ad0444b6..ebe7e14338 100644 --- a/internal/cmd/kafka/command_topic.go +++ b/internal/cmd/kafka/command_topic.go @@ -14,6 +14,7 @@ import ( "strings" v1 "github.com/confluentinc/cli/internal/pkg/config/v1" + v3 "github.com/confluentinc/cli/internal/pkg/config/v3" "github.com/c-bata/go-prompt" @@ -57,6 +58,7 @@ type hasAPIKeyTopicCommand struct { } type authenticatedTopicCommand struct { *pcmd.AuthenticatedStateFlagCommand + prerunner pcmd.PreRunner logger *log.Logger clientID string completableChildren []*cobra.Command @@ -73,30 +75,47 @@ type topicData struct { } // NewTopicCommand returns the Cobra command for Kafka topic. -func NewTopicCommand(isAPIKeyLogin bool, prerunner pcmd.PreRunner, logger *log.Logger, clientID string) *kafkaTopicCommand { - command := &cobra.Command{ +func NewTopicCommand(cfg *v3.Config, isAPIKeyLogin bool, prerunner pcmd.PreRunner, logger *log.Logger, clientID string) *kafkaTopicCommand { + cmd := &cobra.Command{ Use: "topic", Short: "Manage Kafka topics.", } + hasAPIKeyCmd := &hasAPIKeyTopicCommand{ - HasAPIKeyCLICommand: pcmd.NewHasAPIKeyCLICommand(command, prerunner, ProduceAndConsumeFlags), + HasAPIKeyCLICommand: pcmd.NewHasAPIKeyCLICommand(cmd, prerunner, ProduceAndConsumeFlags), prerunner: prerunner, logger: logger, clientID: clientID, } + hasAPIKeyCmd.init() kafkaTopicCommand := &kafkaTopicCommand{ hasAPIKeyTopicCommand: hasAPIKeyCmd, } + if !isAPIKeyLogin { - authenticatedCmd := &authenticatedTopicCommand{ - AuthenticatedStateFlagCommand: pcmd.NewAuthenticatedStateFlagCommand(command, prerunner, TopicSubcommandFlags), + flagMap := OnPremTopicSubcommandFlags + if cfg.IsCloudLogin() { + flagMap = TopicSubcommandFlags + } + + c := &authenticatedTopicCommand{ + AuthenticatedStateFlagCommand: pcmd.NewAuthenticatedStateFlagCommand(cmd, prerunner, flagMap), + prerunner: prerunner, logger: logger, clientID: clientID, } - authenticatedCmd.init() - kafkaTopicCommand.authenticatedTopicCommand = authenticatedCmd + + if cfg.IsCloudLogin() { + c.init() + } else { + c.SetPersistentPreRunE(prerunner.InitializeOnPremKafkaRest(c.AuthenticatedCLICommand)) + c.onPremInit() + } + + kafkaTopicCommand.authenticatedTopicCommand = c } + return kafkaTopicCommand } @@ -133,10 +152,11 @@ func (k *kafkaTopicCommand) ServerCompletableChildren() []*cobra.Command { func (h *hasAPIKeyTopicCommand) init() { cmd := &cobra.Command{ - Use: "produce ", - Short: "Produce messages to a Kafka topic.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(h.produce), + Use: "produce ", + Short: "Produce messages to a Kafka topic.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(h.produce), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, } cmd.Flags().String("delimiter", ":", "The key/value delimiter.") cmd.Flags().String("value-format", "string", "Format of message value as string, avro, protobuf, or jsonschema.") @@ -148,10 +168,11 @@ func (h *hasAPIKeyTopicCommand) init() { h.AddCommand(cmd) cmd = &cobra.Command{ - Use: "consume ", - Short: "Consume messages from a Kafka topic.", - Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(h.consume), + Use: "consume ", + Short: "Consume messages from a Kafka topic.", + Args: cobra.ExactArgs(1), + RunE: pcmd.NewCLIRunE(h.consume), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Consume items from the `my_topic` topic and press `Ctrl+C` to exit.", diff --git a/internal/cmd/kafka/command_topic_onprem.go b/internal/cmd/kafka/command_topic_onprem.go index 5e91800ee5..eb99fedb8e 100644 --- a/internal/cmd/kafka/command_topic_onprem.go +++ b/internal/cmd/kafka/command_topic_onprem.go @@ -16,17 +16,11 @@ import ( pcmd "github.com/confluentinc/cli/internal/pkg/cmd" "github.com/confluentinc/cli/internal/pkg/errors" "github.com/confluentinc/cli/internal/pkg/examples" - kafka "github.com/confluentinc/cli/internal/pkg/kafka" + "github.com/confluentinc/cli/internal/pkg/kafka" "github.com/confluentinc/cli/internal/pkg/output" "github.com/confluentinc/cli/internal/pkg/utils" ) -// Info needed to complete kafka topic ... -type topicCommand struct { - *pcmd.AuthenticatedStateFlagCommand - prerunner pcmd.PreRunner -} - type PartitionData struct { TopicName string `json:"topic" yaml:"topic"` PartitionId int32 `json:"partition" yaml:"partition"` @@ -43,28 +37,13 @@ type TopicData struct { Configs map[string]string `json:"config" yaml:"config"` } -// Return the command to be registered to the kafka topic slot -func NewTopicCommandOnPrem(prerunner pcmd.PreRunner) *cobra.Command { - topicCmd := &topicCommand{ - AuthenticatedStateFlagCommand: pcmd.NewAuthenticatedStateFlagCommand( - &cobra.Command{ - Use: "topic", - Short: "Manage Kafka topics.", - }, prerunner, OnPremTopicSubcommandFlags), - prerunner: prerunner, - } - topicCmd.SetPersistentPreRunE(prerunner.InitializeOnPremKafkaRest(topicCmd.AuthenticatedCLICommand)) - topicCmd.init() - return topicCmd.Command -} - // Register each of the verbs and expected args -func (topicCmd *topicCommand) init() { +func (c *authenticatedTopicCommand) onPremInit() { // Register list command listCmd := &cobra.Command{ Use: "list", Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(topicCmd.listTopics), + RunE: pcmd.NewCLIRunE(c.onPremList), Short: "List Kafka topics.", Example: examples.BuildExampleString( examples.Example{ @@ -77,13 +56,13 @@ func (topicCmd *topicCommand) init() { listCmd.Flags().AddFlagSet(pcmd.OnPremKafkaRestSet()) //includes url, ca-cert-path, client-cert-path, client-key-path, and no-auth flags listCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) listCmd.Flags().SortFlags = false - topicCmd.AddCommand(listCmd) + c.AddCommand(listCmd) createCmd := &cobra.Command{ Use: "create ", Short: "Create a Kafka topic.", Args: cobra.ExactArgs(1), // - RunE: pcmd.NewCLIRunE(topicCmd.createTopic), + RunE: pcmd.NewCLIRunE(c.onPremCreate), Example: examples.BuildExampleString( examples.Example{ Text: "Create a topic named `my_topic` with default options at specified cluster (providing Kafka REST Proxy endpoint).", @@ -100,13 +79,13 @@ func (topicCmd *topicCommand) init() { createCmd.Flags().StringSlice("config", nil, "A comma-separated list of topic configuration ('key=value') overrides for the topic being created.") createCmd.Flags().Bool("if-not-exists", false, "Exit gracefully if topic already exists.") createCmd.Flags().SortFlags = false - topicCmd.AddCommand(createCmd) + c.AddCommand(createCmd) deleteCmd := &cobra.Command{ Use: "delete ", Short: "Delete a Kafka topic.", Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(topicCmd.deleteTopic), + RunE: pcmd.NewCLIRunE(c.onPremDelete), Example: examples.BuildExampleString( examples.Example{ Text: "Delete the topic `my_topic` at specified cluster (providing Kafka REST Proxy endpoint). Use this command carefully as data loss can occur.", @@ -115,13 +94,13 @@ func (topicCmd *topicCommand) init() { } deleteCmd.Flags().AddFlagSet(pcmd.OnPremKafkaRestSet()) //includes url, ca-cert-path, client-cert-path, client-key-path, and no-auth flags deleteCmd.Flags().SortFlags = false - topicCmd.AddCommand(deleteCmd) + c.AddCommand(deleteCmd) updateCmd := &cobra.Command{ Use: "update ", Short: "Update a Kafka topic.", Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(topicCmd.updateTopicConfig), + RunE: pcmd.NewCLIRunE(c.onPremUpdate), Example: examples.BuildExampleString( examples.Example{ Text: "Modify the `my_topic` topic at specified cluster (providing Kafka REST Proxy endpoint) to have a retention period of 3 days (259200000 milliseconds).", @@ -132,12 +111,12 @@ func (topicCmd *topicCommand) init() { updateCmd.Flags().StringSlice("config", nil, "A comma-separated list of topics configuration ('key=value') overrides for the topic being created.") updateCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) updateCmd.Flags().SortFlags = false - topicCmd.AddCommand(updateCmd) + c.AddCommand(updateCmd) describeCmd := &cobra.Command{ Use: "describe ", Args: cobra.ExactArgs(1), - RunE: pcmd.NewCLIRunE(topicCmd.describeTopic), + RunE: pcmd.NewCLIRunE(c.onPremDescribe), Short: "Describe a Kafka topic.", Example: examples.BuildExampleString( examples.Example{ @@ -149,7 +128,7 @@ func (topicCmd *topicCommand) init() { describeCmd.Flags().AddFlagSet(pcmd.OnPremKafkaRestSet()) //includes url, ca-cert-path, client-cert-path, client-key-path, and no-auth flags describeCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) describeCmd.Flags().SortFlags = false - topicCmd.AddCommand(describeCmd) + c.AddCommand(describeCmd) } //List Kafka topics. @@ -165,8 +144,8 @@ func (topicCmd *topicCommand) init() { //--no-auth Include if requests should be made without authentication headers, and user will not be prompted for credentials. //-o, --output string Specify the output format as "human", "json", or "yaml". (default "human") //--context string CLI Context name. -func (topicCmd *topicCommand) listTopics(cmd *cobra.Command, args []string) error { - restClient, restContext, err := initKafkaRest(topicCmd.AuthenticatedCLICommand, cmd) +func (c *authenticatedTopicCommand) onPremList(cmd *cobra.Command, _ []string) error { + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } @@ -209,10 +188,10 @@ func (topicCmd *topicCommand) listTopics(cmd *cobra.Command, args []string) erro //--config strings A comma-separated list of topic configuration ('key=value') overrides for the topic being created. //--if-not-exists Exit gracefully if topic already exists. //--context string CLI Context name. -func (topicCmd *topicCommand) createTopic(cmd *cobra.Command, args []string) error { +func (c *authenticatedTopicCommand) onPremCreate(cmd *cobra.Command, args []string) error { // Parse arguments topicName := args[0] - restClient, restContext, err := initKafkaRest(topicCmd.AuthenticatedCLICommand, cmd) + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } @@ -295,10 +274,10 @@ func (topicCmd *topicCommand) createTopic(cmd *cobra.Command, args []string) err //--client-key-path string Path to client private key, include for mTLS authentication. //--no-auth Include if requests should be made without authentication headers, and user will not be prompted for credentials. //--context string CLI Context name. -func (topicCmd *topicCommand) deleteTopic(cmd *cobra.Command, args []string) error { +func (c *authenticatedTopicCommand) onPremDelete(cmd *cobra.Command, args []string) error { // Parse arguments topicName := args[0] - restClient, restContext, err := initKafkaRest(topicCmd.AuthenticatedCLICommand, cmd) + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } @@ -328,7 +307,7 @@ func (topicCmd *topicCommand) deleteTopic(cmd *cobra.Command, args []string) err //--no-auth Include if requests should be made without authentication headers, and user will not be prompted for credentials. //--config strings A comma-separated list of topics configuration ('key=value') overrides for the topic being created. //--context string CLI Context name. -func (topicCmd *topicCommand) updateTopicConfig(cmd *cobra.Command, args []string) error { +func (c *authenticatedTopicCommand) onPremUpdate(cmd *cobra.Command, args []string) error { // Parse Argument topicName := args[0] format, err := cmd.Flags().GetString(output.FlagName) @@ -337,7 +316,7 @@ func (topicCmd *topicCommand) updateTopicConfig(cmd *cobra.Command, args []strin } else if !output.IsValidFormatString(format) { // catch format flag return output.NewInvalidOutputFormatFlagError(format) } - restClient, restContext, err := initKafkaRest(topicCmd.AuthenticatedCLICommand, cmd) + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } @@ -414,7 +393,7 @@ func (topicCmd *topicCommand) updateTopicConfig(cmd *cobra.Command, args []strin //--no-auth Include if requests should be made without authentication headers, and user will not be prompted for credentials. //-o, --output string Specify the output format as "human", "json", or "yaml". (default "human") //--context string CLI Context name. -func (topicCmd *topicCommand) describeTopic(cmd *cobra.Command, args []string) error { +func (c *authenticatedTopicCommand) onPremDescribe(cmd *cobra.Command, args []string) error { // Parse Args topicName := args[0] format, err := cmd.Flags().GetString(output.FlagName) @@ -423,7 +402,7 @@ func (topicCmd *topicCommand) describeTopic(cmd *cobra.Command, args []string) e } else if !output.IsValidFormatString(format) { // catch format flag return output.NewInvalidOutputFormatFlagError(format) } - restClient, restContext, err := initKafkaRest(topicCmd.AuthenticatedCLICommand, cmd) + restClient, restContext, err := initKafkaRest(c.AuthenticatedCLICommand, cmd) if err != nil { return err } diff --git a/internal/cmd/kafka/command_topic_onprem_test.go b/internal/cmd/kafka/command_topic_onprem_test.go index 91e32b90bb..14b0c80c75 100644 --- a/internal/cmd/kafka/command_topic_onprem_test.go +++ b/internal/cmd/kafka/command_topic_onprem_test.go @@ -4,14 +4,13 @@ import ( "bytes" "context" "fmt" - - v3 "github.com/confluentinc/cli/internal/pkg/config/v3" - "net/http" purl "net/url" "strings" "testing" + v3 "github.com/confluentinc/cli/internal/pkg/config/v3" + "github.com/confluentinc/cli/internal/pkg/cmd" "github.com/confluentinc/cli/internal/pkg/errors" @@ -130,7 +129,7 @@ func checkURL(url string) error { return nil } -// Create a new topicCommand. Should be called before each test case. +// Create a new authenticatedTopicCommand. Should be called before each test case. func (suite *KafkaTopicOnPremTestSuite) createCommand() *cobra.Command { // Define testAPIClient suite.testClient = kafkarestv3.NewAPIClient(kafkarestv3.NewConfiguration()) @@ -234,9 +233,10 @@ func (suite *KafkaTopicOnPremTestSuite) createCommand() *cobra.Command { return *suite.replicaList, nil, nil }, } + conf = v3.AuthenticatedConfluentConfigMock() provider := suite.getRestProvider() - testPrerunner := cliMock.NewPreRunnerMock(nil, nil, &provider, v3.AuthenticatedConfluentConfigMock()) - return NewTopicCommandOnPrem(testPrerunner) + testPrerunner := cliMock.NewPreRunnerMock(nil, nil, &provider, conf) + return NewTopicCommand(conf, false, testPrerunner, nil, "").Command } // Executes the given command with the given args, returns the command executed, stdout and error. diff --git a/internal/cmd/kafka/command_topic_test.go b/internal/cmd/kafka/command_topic_test.go index d9f8e82a1d..4e5bfb760c 100644 --- a/internal/cmd/kafka/command_topic_test.go +++ b/internal/cmd/kafka/command_topic_test.go @@ -40,7 +40,7 @@ func (suite *KafkaTopicTestSuite) newCmd(conf *v3.Config) *kafkaTopicCommand { }, } prerunner := cliMock.NewPreRunnerMock(client, nil, nil, conf) - cmd := NewTopicCommand(false, prerunner, nil, "id") + cmd := NewTopicCommand(conf, false, prerunner, nil, "id") return cmd } diff --git a/internal/cmd/ksql/command.go b/internal/cmd/ksql/command.go index da16299bf9..c520a3c81f 100644 --- a/internal/cmd/ksql/command.go +++ b/internal/cmd/ksql/command.go @@ -21,8 +21,9 @@ type command struct { func New(cfg *v3.Config, prerunner pcmd.PreRunner, serverCompleter completer.ServerSideCompleter, analyticsClient analytics.Client) *cobra.Command { cliCmd := pcmd.NewCLICommand( &cobra.Command{ - Use: "ksql", - Short: "Manage ksqlDB applications.", + Use: "ksql", + Short: "Manage ksqlDB applications.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLoginOrOnPremLogin}, }, prerunner) cmd := &command{ CLICommand: cliCmd, @@ -35,11 +36,12 @@ func New(cfg *v3.Config, prerunner pcmd.PreRunner, serverCompleter completer.Ser } func (c *command) init(cfg *v3.Config) { - if cfg.IsCloud() { - clusterCmd := NewClusterCommand(c.prerunner, c.analyticsClient) - c.AddCommand(clusterCmd.Command) + clusterCmd := NewClusterCommand(c.prerunner, c.analyticsClient) + + c.AddCommand(clusterCmd.Command) + c.AddCommand(NewClusterCommandOnPrem(c.prerunner)) + + if cfg.IsCloudLogin() { c.serverCompleter.AddCommand(clusterCmd) - } else { - c.AddCommand(NewClusterCommandOnPrem(c.prerunner)) } } diff --git a/internal/cmd/ksql/command_cluster.go b/internal/cmd/ksql/command_cluster.go index e2fc4c6e2f..09b8466ec5 100644 --- a/internal/cmd/ksql/command_cluster.go +++ b/internal/cmd/ksql/command_cluster.go @@ -42,8 +42,9 @@ type clusterCommand struct { func NewClusterCommand(prerunner pcmd.PreRunner, analyticsClient analytics.Client) *clusterCommand { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "app", - Short: "Manage ksqlDB apps.", + Use: "app", + Short: "Manage ksqlDB apps.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, }, prerunner, SubcommandFlags) cmd := &clusterCommand{AuthenticatedStateFlagCommand: cliCmd, analyticsClient: analyticsClient} cmd.prerunner = prerunner diff --git a/internal/cmd/ksql/command_cluster_onprem.go b/internal/cmd/ksql/command_cluster_onprem.go index 212d0408c4..3d316d76c2 100644 --- a/internal/cmd/ksql/command_cluster_onprem.go +++ b/internal/cmd/ksql/command_cluster_onprem.go @@ -23,8 +23,9 @@ type clusterCommandOnPrem struct { func NewClusterCommandOnPrem(prerunner pcmd.PreRunner) *cobra.Command { cliCmd := pcmd.NewAuthenticatedWithMDSStateFlagCommand( &cobra.Command{ - Use: "cluster", - Short: "Manage ksqlDB clusters.", + Use: "cluster", + Short: "Manage ksqlDB clusters.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, }, prerunner, OnPremClusterSubcommandFlags) cmd := &clusterCommandOnPrem{ diff --git a/internal/cmd/logout/command.go b/internal/cmd/logout/command.go index e662fa0104..41c92a64ce 100644 --- a/internal/cmd/logout/command.go +++ b/internal/cmd/logout/command.go @@ -35,9 +35,9 @@ func New(cfg *v3.Config, prerunner pcmd.PreRunner, analyticsClient analytics.Cli func (a *Command) init(prerunner pcmd.PreRunner) { context := "Confluent Cloud or Confluent Platform" - if a.cfg.IsCloud() { + if a.cfg.IsCloudLogin() { context = "Confluent Cloud" - } else if a.cfg.IsOnPrem() { + } else if a.cfg.IsOnPremLogin() { context = "Confluent Platform" } @@ -62,7 +62,7 @@ func (a *Command) logout(cmd *cobra.Command, _ []string) error { level = a.Config.Logger.GetLevel() } - username, err := a.netrcHandler.RemoveNetrcCredentials(a.cfg.IsCloud(), a.Config.Config.Context().Name) + username, err := a.netrcHandler.RemoveNetrcCredentials(a.cfg.IsCloudLogin(), a.Config.Config.Context().Name) if err == nil { if level >= log.WARN { utils.ErrPrintf(cmd, errors.RemoveNetrcCredentialsMsg, username, a.netrcHandler.GetFileName()) diff --git a/internal/cmd/price/command.go b/internal/cmd/price/command.go index 866cb84d15..fdbfde9e99 100644 --- a/internal/cmd/price/command.go +++ b/internal/cmd/price/command.go @@ -9,7 +9,7 @@ import ( orgv1 "github.com/confluentinc/cc-structs/kafka/org/v1" "github.com/spf13/cobra" - "github.com/confluentinc/cli/internal/pkg/cmd" + pcmd "github.com/confluentinc/cli/internal/pkg/cmd" "github.com/confluentinc/cli/internal/pkg/output" "github.com/confluentinc/cli/internal/pkg/utils" ) @@ -66,16 +66,17 @@ var ( ) type command struct { - *cmd.AuthenticatedCLICommand + *pcmd.AuthenticatedCLICommand } -func New(prerunner cmd.PreRunner) *cobra.Command { +func New(prerunner pcmd.PreRunner) *cobra.Command { c := &command{ - cmd.NewAuthenticatedCLICommand( + pcmd.NewAuthenticatedCLICommand( &cobra.Command{ - Use: "price", - Short: "See Confluent Cloud pricing information.", - Args: cobra.NoArgs, + Use: "price", + Short: "See Confluent Cloud pricing information.", + Args: cobra.NoArgs, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner, ), @@ -91,7 +92,7 @@ func (c *command) newListCommand() *cobra.Command { Use: "list", Short: "Print an organization's price list.", Args: cobra.NoArgs, - RunE: cmd.NewCLIRunE(c.list), + RunE: pcmd.NewCLIRunE(c.list), } // Required flags diff --git a/internal/cmd/prompt/command.go b/internal/cmd/prompt/command.go index 9152a08bf3..29811d0b0d 100644 --- a/internal/cmd/prompt/command.go +++ b/internal/cmd/prompt/command.go @@ -127,15 +127,16 @@ func New(cfg *v3.Config, prerunner pcmd.PreRunner, ps1 *ps1.Prompt, logger *log. func (c *promptCommand) init(cfg *v3.Config, prerunner pcmd.PreRunner) { promptCmd := &cobra.Command{ - Use: "prompt", - Short: fmt.Sprintf("Print %s context for your terminal prompt.", version.FullCLIName), - Long: parseTemplate(longDescriptionTemplate, version.CLIName), - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.prompt), + Use: "prompt", + Short: fmt.Sprintf("Print %s context for your terminal prompt.", version.FullCLIName), + Long: parseTemplate(longDescriptionTemplate, version.CLIName), + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.prompt), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, } // Ideally we'd default to %c but contexts are implicit today with uber-verbose names like `login-cody@confluent.io-https://devel.cpdev.cloud` defaultFormat := `({{color "blue" "confluent"}}|{{color "red" "%E"}}:{{color "cyan" "%K"}})` - if cfg.IsOnPrem() { + if cfg.IsOnPremLogin() { defaultFormat = `({{color "blue" "confluent"}}|{{color "cyan" "%K"}})` } promptCmd.Flags().StringP("format", "f", defaultFormat, "The format string to use. See the help for details.") diff --git a/internal/cmd/schema-registry/command.go b/internal/cmd/schema-registry/command.go index 6e07e0aeb9..8065024dc0 100644 --- a/internal/cmd/schema-registry/command.go +++ b/internal/cmd/schema-registry/command.go @@ -23,8 +23,9 @@ type command struct { func New(cfg *v3.Config, prerunner pcmd.PreRunner, srClient *srsdk.APIClient, logger *log.Logger, analyticsClient analytics.Client) *cobra.Command { cliCmd := pcmd.NewAuthenticatedCLICommand( &cobra.Command{ - Use: "schema-registry", - Short: `Manage Schema Registry.`, + Use: "schema-registry", + Short: `Manage Schema Registry.`, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLoginOrOnPremLogin}, }, prerunner) cmd := &command{ AuthenticatedCLICommand: cliCmd, @@ -38,11 +39,7 @@ func New(cfg *v3.Config, prerunner pcmd.PreRunner, srClient *srsdk.APIClient, lo } func (c *command) init(cfg *v3.Config) { - if cfg.IsCloud() { - c.AddCommand(NewClusterCommand(c.prerunner, c.srClient, c.logger, c.analyticsClient)) - c.AddCommand(NewSubjectCommand(c.prerunner, c.srClient)) - c.AddCommand(NewSchemaCommand(c.prerunner, c.srClient)) - } else { - c.AddCommand(NewClusterCommandOnPrem(c.prerunner)) - } + c.AddCommand(NewClusterCommand(cfg, c.prerunner, c.srClient, c.logger, c.analyticsClient)) + c.AddCommand(NewSchemaCommand(c.prerunner, c.srClient)) + c.AddCommand(NewSubjectCommand(c.prerunner, c.srClient)) } diff --git a/internal/cmd/schema-registry/command_cluster.go b/internal/cmd/schema-registry/command_cluster.go index 0cc68824e3..a75c186e28 100644 --- a/internal/cmd/schema-registry/command_cluster.go +++ b/internal/cmd/schema-registry/command_cluster.go @@ -15,6 +15,7 @@ import ( "github.com/confluentinc/cli/internal/pkg/analytics" pcmd "github.com/confluentinc/cli/internal/pkg/cmd" v2 "github.com/confluentinc/cli/internal/pkg/config/v2" + v3 "github.com/confluentinc/cli/internal/pkg/config/v3" "github.com/confluentinc/cli/internal/pkg/errors" "github.com/confluentinc/cli/internal/pkg/examples" "github.com/confluentinc/cli/internal/pkg/log" @@ -45,34 +46,53 @@ var ( type clusterCommand struct { *pcmd.AuthenticatedStateFlagCommand + prerunner pcmd.PreRunner logger *log.Logger srClient *srsdk.APIClient analyticsClient analytics.Client } -func NewClusterCommand(prerunner pcmd.PreRunner, srClient *srsdk.APIClient, logger *log.Logger, analyticsClient analytics.Client) *cobra.Command { - cliCmd := pcmd.NewAuthenticatedStateFlagCommand( - &cobra.Command{ - Use: "cluster", - Short: "Manage Schema Registry cluster.", - Long: "Manage the Schema Registry cluster for the current environment.", - }, prerunner, ClusterSubcommandFlags) - clusterCmd := &clusterCommand{ - AuthenticatedStateFlagCommand: cliCmd, - srClient: srClient, - logger: logger, - analyticsClient: analyticsClient, +func NewClusterCommand(cfg *v3.Config, prerunner pcmd.PreRunner, srClient *srsdk.APIClient, logger *log.Logger, analyticsClient analytics.Client) *cobra.Command { + cmd := &cobra.Command{ + Use: "cluster", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLoginOrOnPremLogin}, } - clusterCmd.init() - return clusterCmd.Command + + if cfg.IsCloudLogin() { + cmd.Short = "Manage Schema Registry cluster." + cmd.Long = "Manage the Schema Registry cluster for the current environment." + } else { + cmd.Short = "Manage Schema Registry clusters." + } + + c := &clusterCommand{ + prerunner: prerunner, + srClient: srClient, + logger: logger, + analyticsClient: analyticsClient, + } + + if cfg.IsCloudLogin() { + c.AuthenticatedStateFlagCommand = pcmd.NewAuthenticatedStateFlagCommand(cmd, prerunner, ClusterSubcommandFlags) + } else { + c.AuthenticatedStateFlagCommand = pcmd.NewAuthenticatedWithMDSStateFlagCommand(cmd, prerunner, OnPremClusterSubcommandFlags) + } + + c.AddCommand(c.newDescribeCommand()) + c.AddCommand(c.newEnableCommand()) + c.AddCommand(c.newListCommand()) + c.AddCommand(c.newUpdateCommand()) + + return c.Command } -func (c *clusterCommand) init() { - createCmd := &cobra.Command{ - Use: "enable", - Short: "Enable Schema Registry for this environment.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.enable), +func (c *clusterCommand) newEnableCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "enable", + Short: "Enable Schema Registry for this environment.", + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.enable), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Enable Schema Registry, using Google Cloud Platform in the US:", @@ -80,29 +100,40 @@ func (c *clusterCommand) init() { }, ), } - createCmd.Flags().String("cloud", "", "Cloud provider (e.g. 'aws', 'azure', or 'gcp').") - _ = createCmd.MarkFlagRequired("cloud") - createCmd.Flags().String("geo", "", "Either 'us', 'eu', or 'apac'.") - _ = createCmd.MarkFlagRequired("geo") - createCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) - createCmd.Flags().SortFlags = false - c.AddCommand(createCmd) - - describeCmd := &cobra.Command{ - Use: "describe", - Short: "Describe the Schema Registry cluster for this environment.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.describe), + + cmd.Flags().String("cloud", "", "Cloud provider (e.g. 'aws', 'azure', or 'gcp').") + cmd.Flags().String("geo", "", "Either 'us', 'eu', or 'apac'.") + cmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) + cmd.Flags().SortFlags = false + + _ = cmd.MarkFlagRequired("cloud") + _ = cmd.MarkFlagRequired("geo") + + return cmd +} + +func (c *clusterCommand) newDescribeCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "describe", + Short: "Describe the Schema Registry cluster for this environment.", + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.describe), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, } - describeCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) - describeCmd.Flags().SortFlags = false - c.AddCommand(describeCmd) - - updateCmd := &cobra.Command{ - Use: "update", - Short: "Update global mode or compatibility of Schema Registry.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.update), + + cmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) + cmd.Flags().SortFlags = false + + return cmd +} + +func (c *clusterCommand) newUpdateCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "update", + Short: "Update global mode or compatibility of Schema Registry.", + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.update), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, Example: examples.BuildExampleString( examples.Example{ Text: "Update top level compatibility or mode of Schema Registry.", @@ -110,10 +141,12 @@ func (c *clusterCommand) init() { }, ), } - updateCmd.Flags().String("compatibility", "", "Can be BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE, FULL, FULL_TRANSITIVE, or NONE.") - updateCmd.Flags().String("mode", "", "Can be READWRITE, READ, OR WRITE.") - updateCmd.Flags().SortFlags = false - c.AddCommand(updateCmd) + + cmd.Flags().String("compatibility", "", "Can be BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE, FULL, FULL_TRANSITIVE, or NONE.") + cmd.Flags().String("mode", "", "Can be READWRITE, READ, OR WRITE.") + cmd.Flags().SortFlags = false + + return cmd } func (c *clusterCommand) enable(cmd *cobra.Command, _ []string) error { diff --git a/internal/cmd/schema-registry/command_cluster_onprem.go b/internal/cmd/schema-registry/command_cluster_onprem.go index 30854053b3..0f2d3b6d35 100644 --- a/internal/cmd/schema-registry/command_cluster_onprem.go +++ b/internal/cmd/schema-registry/command_cluster_onprem.go @@ -14,45 +14,27 @@ import ( var clusterType = "schema-registry-cluster" -type clusterCommandOnPrem struct { - *pcmd.AuthenticatedStateFlagCommand - prerunner pcmd.PreRunner -} - -// NewClusterCommand returns the Cobra command for Kafka cluster. -func NewClusterCommandOnPrem(prerunner pcmd.PreRunner) *cobra.Command { - cliCmd := pcmd.NewAuthenticatedWithMDSStateFlagCommand( - &cobra.Command{ - Use: "cluster", - Short: "Manage Schema Registry clusters.", - }, - prerunner, OnPremClusterSubcommandFlags) - cmd := &clusterCommandOnPrem{ - AuthenticatedStateFlagCommand: cliCmd, - prerunner: prerunner, +func (c *clusterCommand) newListCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List registered Schema Registry clusters.", + Long: "List Schema Registry clusters that are registered with the MDS cluster registry.", + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.list), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, } - cmd.init() - return cmd.Command -} -func (c *clusterCommandOnPrem) init() { - listCmd := &cobra.Command{ - Use: "list", - Short: "List registered Schema Registry clusters.", - Long: "List Schema Registry clusters that are registered with the MDS cluster registry.", - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.list), - } - listCmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) - listCmd.Flags().SortFlags = false - c.AddCommand(listCmd) + cmd.Flags().StringP(output.FlagName, output.ShortHandFlag, output.DefaultValue, output.Usage) + cmd.Flags().SortFlags = false + + return cmd } -func (c *clusterCommandOnPrem) createContext() context.Context { +func (c *clusterCommand) createContext() context.Context { return context.WithValue(context.Background(), mds.ContextAccessToken, c.State.AuthToken) } -func (c *clusterCommandOnPrem) list(cmd *cobra.Command, _ []string) error { +func (c *clusterCommand) list(cmd *cobra.Command, _ []string) error { schemaClustertype := &mds.ClusterRegistryListOpts{ ClusterType: optional.NewString(clusterType), } diff --git a/internal/cmd/schema-registry/command_schema.go b/internal/cmd/schema-registry/command_schema.go index d9d08f682a..c52095b37c 100644 --- a/internal/cmd/schema-registry/command_schema.go +++ b/internal/cmd/schema-registry/command_schema.go @@ -26,8 +26,9 @@ type schemaCommand struct { func NewSchemaCommand(prerunner pcmd.PreRunner, srClient *srsdk.APIClient) *cobra.Command { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "schema", - Short: "Manage Schema Registry schemas.", + Use: "schema", + Short: "Manage Schema Registry schemas.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, }, prerunner, SchemaSubcommandFlags) schemaCmd := &schemaCommand{ AuthenticatedStateFlagCommand: cliCmd, diff --git a/internal/cmd/schema-registry/command_subject.go b/internal/cmd/schema-registry/command_subject.go index 779d33efe5..b88d2e785c 100644 --- a/internal/cmd/schema-registry/command_subject.go +++ b/internal/cmd/schema-registry/command_subject.go @@ -24,8 +24,9 @@ type subjectCommand struct { func NewSubjectCommand(prerunner pcmd.PreRunner, srClient *srsdk.APIClient) *cobra.Command { cliCmd := pcmd.NewAuthenticatedStateFlagCommand( &cobra.Command{ - Use: "subject", - Short: "Manage Schema Registry subjects.", + Use: "subject", + Short: "Manage Schema Registry subjects.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin}, }, prerunner, SubjectSubcommandFlags) subjectCmd := &subjectCommand{ AuthenticatedStateFlagCommand: cliCmd, diff --git a/internal/cmd/secret/command.go b/internal/cmd/secret/command.go index 339c73d42e..a806d2f9af 100644 --- a/internal/cmd/secret/command.go +++ b/internal/cmd/secret/command.go @@ -8,23 +8,26 @@ import ( ) type command struct { - *cobra.Command + *pcmd.CLICommand resolv pcmd.FlagResolver plugin secret.PasswordProtection } // New returns the default command object for Password Protection -func New(resolv pcmd.FlagResolver, plugin secret.PasswordProtection) *cobra.Command { - cmd := &command{ - Command: &cobra.Command{ - Use: "secret", - Short: "Manage secrets for Confluent Platform.", - }, - resolv: resolv, - plugin: plugin, +func New(prerunner pcmd.PreRunner, resolv pcmd.FlagResolver, plugin secret.PasswordProtection) *cobra.Command { + cmd := &cobra.Command{ + Use: "secret", + Short: "Manage secrets for Confluent Platform.", + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireOnPremLogin}, } - cmd.init() - return cmd.Command + + c := &command{ + CLICommand: pcmd.NewAnonymousCLICommand(cmd, prerunner), + resolv: resolv, + plugin: plugin, + } + c.init() + return c.Command } func (c *command) init() { diff --git a/internal/cmd/service-account/command.go b/internal/cmd/service-account/command.go index 17b8e9a396..4a1bc87f81 100644 --- a/internal/cmd/service-account/command.go +++ b/internal/cmd/service-account/command.go @@ -40,8 +40,9 @@ const descriptionLength = 128 func New(prerunner pcmd.PreRunner, analyticsClient analytics.Client) *command { cliCmd := pcmd.NewAuthenticatedCLICommand( &cobra.Command{ - Use: "service-account", - Short: `Manage service accounts.`, + Use: "service-account", + Short: `Manage service accounts.`, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, }, prerunner) cmd := &command{ AuthenticatedCLICommand: cliCmd, diff --git a/internal/cmd/shell/command.go b/internal/cmd/shell/command.go index 2944edccab..c020168075 100644 --- a/internal/cmd/shell/command.go +++ b/internal/cmd/shell/command.go @@ -45,10 +45,11 @@ func NewShellCmd(rootCmd *cobra.Command, prerunner pcmd.PreRunner, config *v3.Co func (c *command) init() { c.Command = &cobra.Command{ - Use: "shell", - Short: fmt.Sprintf("Run the %s shell.", version.CLIName), - RunE: pcmd.NewCLIRunE(c.shell), - Args: cobra.NoArgs, + Use: "shell", + Short: fmt.Sprintf("Run the %s shell.", version.CLIName), + RunE: pcmd.NewCLIRunE(c.shell), + Args: cobra.NoArgs, + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin}, } } diff --git a/internal/cmd/update/command.go b/internal/cmd/update/command.go index 76ee95379c..d606af58b7 100644 --- a/internal/cmd/update/command.go +++ b/internal/cmd/update/command.go @@ -53,7 +53,7 @@ func NewClient(cliName string, disableUpdateCheck bool, logger *log.Logger) upda } type command struct { - Command *cobra.Command + *pcmd.CLICommand version *pversion.Version logger *log.Logger client update.Client @@ -62,26 +62,28 @@ type command struct { } // New returns the command for the built-in updater. -func New(logger *log.Logger, version *pversion.Version, client update.Client, analytics analytics.Client) *cobra.Command { - cmd := &command{ +func New(prerunner pcmd.PreRunner, logger *log.Logger, version *pversion.Version, client update.Client, analytics analytics.Client) *cobra.Command { + c := &command{ version: version, logger: logger, client: client, analyticsClient: analytics, } - cmd.init() - return cmd.Command -} -func (c *command) init() { - c.Command = &cobra.Command{ - Use: "update", - Short: fmt.Sprintf("Update the %s.", pversion.FullCLIName), - Args: cobra.NoArgs, - RunE: pcmd.NewCLIRunE(c.update), + cmd := &cobra.Command{ + Use: "update", + Short: fmt.Sprintf("Update the %s.", pversion.FullCLIName), + Args: cobra.NoArgs, + RunE: pcmd.NewCLIRunE(c.update), + Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireUpdatesEnabled}, } - c.Command.Flags().BoolP("yes", "y", false, "Update without prompting.") - c.Command.Flags().SortFlags = false + + cmd.Flags().BoolP("yes", "y", false, "Update without prompting.") + cmd.Flags().SortFlags = false + + c.CLICommand = pcmd.NewAnonymousCLICommand(cmd, prerunner) + + return c.Command } func (c *command) update(cmd *cobra.Command, _ []string) error { diff --git a/internal/pkg/analytics/analytics.go b/internal/pkg/analytics/analytics.go index 6fa8c272a5..0e9f5b21b3 100644 --- a/internal/pkg/analytics/analytics.go +++ b/internal/pkg/analytics/analytics.go @@ -360,7 +360,7 @@ func (a *ClientObj) getUser() userInfo { } func (a *ClientObj) getCloudUserInfo() (userId, organizationId, email string) { - if !a.config.HasLogin() { + if !a.config.HasBasicLogin() { return "", "", "" } user := a.config.Context().State.Auth.User @@ -372,7 +372,7 @@ func (a *ClientObj) getCloudUserInfo() (userId, organizationId, email string) { } func (a *ClientObj) getCPUsername() string { - if !a.config.HasLogin() { + if !a.config.HasBasicLogin() { return "" } ctx := a.config.Context() @@ -382,7 +382,7 @@ func (a *ClientObj) getCPUsername() string { func (a *ClientObj) getCredentialType() string { switch a.config.CredentialType() { case v2.Username: - if a.config.HasLogin() { + if a.config.HasBasicLogin() { return v2.Username.String() } case v2.APIKey: diff --git a/internal/pkg/cmd/prerunner.go b/internal/pkg/cmd/prerunner.go index db176e055a..88c0f34215 100644 --- a/internal/pkg/cmd/prerunner.go +++ b/internal/pkg/cmd/prerunner.go @@ -31,7 +31,7 @@ import ( // PreRun is a helper class for automatically setting up Cobra PersistentPreRun commands type PreRunner interface { - Anonymous(command *CLICommand) func(cmd *cobra.Command, args []string) error + Anonymous(command *CLICommand, willAuthenticate bool) func(cmd *cobra.Command, args []string) error Authenticated(command *AuthenticatedCLICommand) func(cmd *cobra.Command, args []string) error AuthenticatedWithMDS(command *AuthenticatedCLICommand) func(cmd *cobra.Command, args []string) error HasAPIKey(command *HasAPIKeyCLICommand) func(cmd *cobra.Command, args []string) error @@ -95,10 +95,7 @@ type HasAPIKeyCLICommand struct { func NewAuthenticatedCLICommand(command *cobra.Command, prerunner PreRunner) *AuthenticatedCLICommand { cmd := &AuthenticatedCLICommand{ - CLICommand: NewCLICommand(command, prerunner), - Context: nil, - State: nil, - KafkaRESTProvider: nil, + CLICommand: NewCLICommand(command, prerunner), } command.PersistentPreRunE = NewCLIPreRunnerE(prerunner.Authenticated(cmd)) cmd.Command = command @@ -138,7 +135,7 @@ func NewAnonymousStateFlagCommand(command *cobra.Command, prerunner PreRunner, f NewAnonymousCLICommand(command, prerunner), flagMap, } - command.PersistentPreRunE = NewCLIPreRunnerE(prerunner.Anonymous(cmd.CLICommand), prerunner.AnonymousParseFlagsIntoContext(cmd.CLICommand)) + command.PersistentPreRunE = NewCLIPreRunnerE(prerunner.Anonymous(cmd.CLICommand, false), prerunner.AnonymousParseFlagsIntoContext(cmd.CLICommand)) cmd.Command = command return cmd } @@ -146,8 +143,6 @@ func NewAnonymousStateFlagCommand(command *cobra.Command, prerunner PreRunner, f func NewAuthenticatedWithMDSCLICommand(command *cobra.Command, prerunner PreRunner) *AuthenticatedCLICommand { cmd := &AuthenticatedCLICommand{ CLICommand: NewCLICommand(command, prerunner), - Context: nil, - State: nil, } command.PersistentPreRunE = NewCLIPreRunnerE(prerunner.AuthenticatedWithMDS(cmd)) cmd.Command = command @@ -157,7 +152,6 @@ func NewAuthenticatedWithMDSCLICommand(command *cobra.Command, prerunner PreRunn func NewHasAPIKeyCLICommand(command *cobra.Command, prerunner PreRunner, flagMap map[string]*pflag.FlagSet) *HasAPIKeyCLICommand { cmd := &HasAPIKeyCLICommand{ CLICommand: NewCLICommand(command, prerunner), - Context: nil, subcommandFlags: flagMap, } command.PersistentPreRunE = NewCLIPreRunnerE(prerunner.HasAPIKey(cmd)) @@ -167,7 +161,7 @@ func NewHasAPIKeyCLICommand(command *cobra.Command, prerunner PreRunner, flagMap func NewAnonymousCLICommand(command *cobra.Command, prerunner PreRunner) *CLICommand { cmd := NewCLICommand(command, prerunner) - command.PersistentPreRunE = NewCLIPreRunnerE(prerunner.Anonymous(cmd)) + command.PersistentPreRunE = NewCLIPreRunnerE(prerunner.Anonymous(cmd, false)) cmd.Command = command return cmd } @@ -232,41 +226,52 @@ func CanCompleteCommand(cmd *cobra.Command) bool { } // Anonymous provides PreRun operations for commands that may be run without a logged-in user -func (r *PreRun) Anonymous(command *CLICommand) func(cmd *cobra.Command, args []string) error { +func (r *PreRun) Anonymous(command *CLICommand, willAuthenticate bool) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { + // Wait for a potential auto-login in the Authenticated PreRun function before checking run requirements. + if !willAuthenticate { + if err := ErrIfMissingRunRequirement(cmd, r.Config); err != nil { + return err + } + } + if _, ok := cmd.Annotations[DoNotTrack]; !ok { r.Analytics.TrackCommand(cmd, args) } - err := command.Config.InitDynamicConfig(cmd, r.Config, r.FlagResolver) - if err != nil { + + if err := command.Config.InitDynamicConfig(cmd, r.Config, r.FlagResolver); err != nil { return err } - command.Version = r.Version + if err := log.SetLoggingVerbosity(cmd, r.Logger); err != nil { return err } r.Logger.Flush() + + command.Version = r.Version if err := r.notifyIfUpdateAvailable(cmd, command.Version.Version); err != nil { return err } + r.warnIfConfluentLocal(cmd) + if r.Config != nil { ctx := command.Config.Context() err := r.ValidateToken(cmd, command.Config) switch err.(type) { case *ccloud.ExpiredTokenError: - err := ctx.DeleteUserAuth() - if err != nil { + if err := ctx.DeleteUserAuth(); err != nil { return err } utils.ErrPrintln(cmd, errors.TokenExpiredMsg) - analyticsError := r.Analytics.SessionTimedOut() - if analyticsError != nil { - r.Logger.Debug(analyticsError.Error()) + if err := r.Analytics.SessionTimedOut(); err != nil { + r.Logger.Debug(err.Error()) } } } + LabelRequiredFlags(cmd) + return nil } } @@ -283,35 +288,38 @@ func LabelRequiredFlags(cmd *cobra.Command) { // Authenticated provides PreRun operations for commands that require a logged-in Confluent Cloud user. func (r *PreRun) Authenticated(command *AuthenticatedCLICommand) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - err := r.Anonymous(command.CLICommand)(cmd, args) - if err != nil { + if err := r.Anonymous(command.CLICommand, true)(cmd, args); err != nil { return err } - err = r.setAuthenticatedContext(cmd, command) - if err != nil { - _, isNotLoggedInError := err.(*errors.NotLoggedInError) - _, isNoContextError := err.(*errors.NotLoggedInError) - if isNotLoggedInError || isNoContextError { - // Attempt Prerun auto login - autoLoginErr := r.ccloudAutoLogin(cmd) - if autoLoginErr != nil { - r.Logger.Debugf("Prerun auto login failed: %s", autoLoginErr.Error()) - return err - } - err = r.setAuthenticatedContext(cmd, command) - if err != nil { - return err + setContextErr := r.setAuthenticatedContext(cmd, command) + if setContextErr != nil { + if _, ok := setContextErr.(*errors.NotLoggedInError); ok { + if !command.Config.IsOnPremLogin() { + if err := r.ccloudAutoLogin(cmd); err != nil { + r.Logger.Debugf("Auto login failed: %v", err) + } else { + setContextErr = r.setAuthenticatedContext(cmd, command) + } } } else { - return err + return setContextErr } } - err = r.ValidateToken(cmd, command.Config) - if err != nil { + // Even if there was an error while setting the context, notify the user about any unmet run requirements first. + if err := ErrIfMissingRunRequirement(cmd, r.Config); err != nil { + return err + } + + if setContextErr != nil { + return setContextErr + } + + if err := r.ValidateToken(cmd, command.Config); err != nil { return err } + return r.setCCloudClient(command) } } @@ -366,10 +374,9 @@ func (r *PreRun) ccloudAutoLogin(cmd *cobra.Command) error { } func (r *PreRun) getCCloudTokenAndCredentials(cmd *cobra.Command) (string, *pauth.Credentials, error) { - url := pauth.CCloudURL netrcFilterParams := netrc.NetrcMachineParams{ CLIName: "ccloud", // Use "ccloud" to maintain compatability with older versions - URL: url, + URL: pauth.CCloudURL, } credentials, err := pauth.GetLoginCredentials( r.LoginCredentialsManager.GetCCloudCredentialsFromEnvVar(cmd), @@ -500,42 +507,47 @@ func (r *PreRun) createCCloudClient(ctx *DynamicContext, cmd *cobra.Command, ver // Authenticated provides PreRun operations for commands that require a logged-in MDS user. func (r *PreRun) AuthenticatedWithMDS(command *AuthenticatedCLICommand) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - err := r.Anonymous(command.CLICommand)(cmd, args) - if err != nil { + if err := r.Anonymous(command.CLICommand, true)(cmd, args); err != nil { return err } - err = r.setAuthenticatedWithMDSContext(command) - if err != nil { - _, isNotLoggedInError := err.(*errors.NotLoggedInError) - _, isNoContextError := err.(*errors.NotLoggedInError) - if isNotLoggedInError || isNoContextError { - // Attempt Prerun auto login - autoLoginErr := r.confluentAutoLogin(cmd) - if autoLoginErr != nil { - r.Logger.Debugf("Prerun auto login failed: %s", autoLoginErr.Error()) - return err - } - err = r.setAuthenticatedWithMDSContext(command) - if err != nil { - return err + setContextErr := r.setAuthenticatedWithMDSContext(command) + if setContextErr != nil { + if _, ok := setContextErr.(*errors.NotLoggedInError); ok { + if !command.Config.IsCloudLogin() { + if err := r.confluentAutoLogin(cmd); err != nil { + r.Logger.Debugf("Auto login failed: %v", err) + } else { + setContextErr = r.setAuthenticatedWithMDSContext(command) + } } } else { - return err + return setContextErr } } + + // Even if there was an error while setting the context, notify the user about any unmet run requirements first. + if err := ErrIfMissingRunRequirement(cmd, r.Config); err != nil { + return err + } + + if setContextErr != nil { + return setContextErr + } + return r.ValidateToken(cmd, command.Config) } } func (r *PreRun) setAuthenticatedWithMDSContext(cliCommand *AuthenticatedCLICommand) error { ctx := cliCommand.Config.Context() - if ctx == nil || !ctx.HasMDSLogin() { + if ctx == nil || !ctx.HasBasicMDSLogin() { return new(errors.NotLoggedInError) } cliCommand.Context = ctx cliCommand.State = ctx.State - return r.setConfluentClient(cliCommand) + r.setConfluentClient(cliCommand) + return nil } func (r *PreRun) confluentAutoLogin(cmd *cobra.Command) error { @@ -577,10 +589,9 @@ func (r *PreRun) getConfluentTokenAndCredentials(cmd *cobra.Command) (string, *p return token, credentials, err } -func (r *PreRun) setConfluentClient(cliCmd *AuthenticatedCLICommand) error { +func (r *PreRun) setConfluentClient(cliCmd *AuthenticatedCLICommand) { ctx := cliCmd.Config.Context() cliCmd.MDSClient = r.createMDSClient(ctx, cliCmd.Version) - return nil } func (r *PreRun) createMDSClient(ctx *DynamicContext, ver *version.Version) *mds.APIClient { @@ -694,8 +705,7 @@ func createOnPremKafkaRestClient(ctx *DynamicContext, caCertPath string, clientC // HasAPIKey provides PreRun operations for commands that require an API key. func (r *PreRun) HasAPIKey(command *HasAPIKeyCLICommand) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - err := r.Anonymous(command.CLICommand)(cmd, args) - if err != nil { + if err := r.Anonymous(command.CLICommand, false)(cmd, args); err != nil { return err } @@ -704,29 +714,32 @@ func (r *PreRun) HasAPIKey(command *HasAPIKeyCLICommand) func(cmd *cobra.Command return new(errors.NotLoggedInError) } command.Context = ctx + var clusterId string if command.Context.Credential.CredentialType == v2.APIKey { clusterId = r.getClusterIdForAPIKeyCredential(ctx) } else if command.Context.Credential.CredentialType == v2.Username { - err := r.ValidateToken(cmd, command.Config) - if err != nil { + if err := r.ValidateToken(cmd, command.Config); err != nil { return err } + client, err := r.createCCloudClient(ctx, cmd, command.Version) if err != nil { return err } ctx.client = client command.Config.Client = client - err = ctx.ParseFlagsIntoContext(cmd, command.Config.Client) - if err != nil { + + if err := ctx.ParseFlagsIntoContext(cmd, command.Config.Client); err != nil { return err } + cluster, err := ctx.GetKafkaClusterForCommand(cmd) if err != nil { return err } clusterId = cluster.ID + key, secret, err := ctx.KeyAndSecretFlags(cmd) if err != nil { return err @@ -744,14 +757,15 @@ func (r *PreRun) HasAPIKey(command *HasAPIKeyCLICommand) func(cmd *cobra.Command } else { panic("Invalid Credential Type") } + hasAPIKey, err := ctx.HasAPIKey(cmd, clusterId) if err != nil { return err } if !hasAPIKey { - err = &errors.UnspecifiedAPIKeyError{ClusterID: clusterId} - return err + return &errors.UnspecifiedAPIKeyError{ClusterID: clusterId} } + return nil } } @@ -802,7 +816,7 @@ func (r *PreRun) updateToken(tokenError error, cmd *cobra.Command, ctx *DynamicC func (r *PreRun) getUpdatedAuthToken(cmd *cobra.Command, ctx *DynamicContext) (string, error) { cliName := "confluent" - if r.Config.IsCloud() { + if r.Config.IsCloudLogin() { cliName = "ccloud" } @@ -816,7 +830,7 @@ func (r *PreRun) getUpdatedAuthToken(cmd *cobra.Command, ctx *DynamicContext) (s } var token string - if r.Config.IsCloud() { + if r.Config.IsCloudLogin() { client := ccloud.NewClient(&ccloud.Params{BaseURL: ctx.Platform.Server, HttpClient: ccloud.BaseClient, Logger: r.Logger, UserAgent: r.Version.UserAgent}) token, _, err = r.AuthTokenHandler.GetCCloudTokens(client, credentials, false) if err != nil { diff --git a/internal/pkg/cmd/run_requirements.go b/internal/pkg/cmd/run_requirements.go new file mode 100644 index 0000000000..b14abcf3bb --- /dev/null +++ b/internal/pkg/cmd/run_requirements.go @@ -0,0 +1,88 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + v2 "github.com/confluentinc/cli/internal/pkg/config/v2" + v3 "github.com/confluentinc/cli/internal/pkg/config/v3" + "github.com/confluentinc/cli/internal/pkg/errors" +) + +const ( + RunRequirement = "run-requirement" + + RequireNonAPIKeyCloudLogin = "non-api-key-cloud-login" + RequireNonAPIKeyCloudLoginOrOnPremLogin = "non-api-key-cloud-login-or-on-prem-login" + RequireCloudLogin = "cloud-login" + RequireCloudLoginOrOnPremLogin = "cloud-login-or-on-prem-login" + RequireOnPremLogin = "on-prem-login" + RequireUpdatesEnabled = "updates-enabled" +) + +const signupSuggestion = `If you need a Confluent Cloud account, sign up with "confluent cloud-signup".` + +var ( + requireCloudLoginErr = errors.NewErrorWithSuggestions( + "you must log in to Confluent Cloud to use this command", + "Log in with \"confluent login\".\n"+signupSuggestion, + ) + requireCloudLoginOrOnPremErr = errors.NewErrorWithSuggestions( + "you must log in to use this command", + "Log in with \"confluent login\".\n"+signupSuggestion, + ) + requireNonAPIKeyCloudLoginErr = errors.NewErrorWithSuggestions( + "you must log in to Confluent Cloud with a username and password to use this command", + "Log in with \"confluent login\".\n"+signupSuggestion, + ) + requireNonAPIKeyCloudLoginOrOnPremLoginErr = errors.NewErrorWithSuggestions( + "you must log in to Confluent Cloud with a username and password or log in to Confluent Platform to use this command", + "Log in with \"confluent login\" or \"confluent login --url \".\n"+signupSuggestion, + ) + requireOnPremLoginErr = errors.NewErrorWithSuggestions( + "you must log in to Confluent Platform to use this command", + `Log in with "confluent login --url ".`, + ) + requireUpdatesEnabledErr = errors.NewErrorWithSuggestions( + "you must enable updates to use this command", + `In ~/.confluent/config.json, set "disable_updates": false`, + ) +) + +// ErrIfMissingRunRequirement returns an error when a command or its parent doesn't meet a requirement; +// for example, an on-prem command shouldn't be used by a cloud user. +func ErrIfMissingRunRequirement(cmd *cobra.Command, cfg *v3.Config) error { + if cmd == nil { + return nil + } + + if requirement, ok := cmd.Annotations[RunRequirement]; ok { + switch requirement { + case RequireCloudLogin: + if !cfg.IsCloudLogin() { + return requireCloudLoginErr + } + case RequireCloudLoginOrOnPremLogin: + if !(cfg.IsCloudLogin() || cfg.IsOnPremLogin()) { + return requireCloudLoginOrOnPremErr + } + case RequireNonAPIKeyCloudLogin: + if !(cfg.CredentialType() != v2.APIKey && cfg.IsCloudLogin()) { + return requireNonAPIKeyCloudLoginErr + } + case RequireNonAPIKeyCloudLoginOrOnPremLogin: + if !(cfg.CredentialType() != v2.APIKey && cfg.IsCloudLogin() || cfg.IsOnPremLogin()) { + return requireNonAPIKeyCloudLoginOrOnPremLoginErr + } + case RequireOnPremLogin: + if !cfg.IsOnPremLogin() { + return requireOnPremLoginErr + } + case RequireUpdatesEnabled: + if cfg.DisableUpdates { + return requireUpdatesEnabledErr + } + } + } + + return ErrIfMissingRunRequirement(cmd.Parent(), cfg) +} diff --git a/internal/pkg/cmd/run_requirements_test.go b/internal/pkg/cmd/run_requirements_test.go new file mode 100644 index 0000000000..f0c506d4a6 --- /dev/null +++ b/internal/pkg/cmd/run_requirements_test.go @@ -0,0 +1,114 @@ +package cmd + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + + "github.com/confluentinc/cli/internal/pkg/config" + v2 "github.com/confluentinc/cli/internal/pkg/config/v2" + v3 "github.com/confluentinc/cli/internal/pkg/config/v3" + testserver "github.com/confluentinc/cli/test/test-server" +) + +var ( + noContextCfg = new(v3.Config) + + cloudCfg = &v3.Config{ + Contexts: map[string]*v3.Context{"cloud": {PlatformName: testserver.TestCloudURL.String()}}, + CurrentContext: "cloud", + IsTest: true, + } + + apiKeyCloudCfg = &v3.Config{ + BaseConfig: &config.BaseConfig{Params: &config.Params{CLIName: "ccloud"}}, // TODO: Remove CLIName + Contexts: map[string]*v3.Context{"cloud": { + PlatformName: testserver.TestCloudURL.String(), + Credential: &v2.Credential{CredentialType: v2.APIKey}, + }}, + CurrentContext: "cloud", + IsTest: true, + } + + nonAPIKeyCloudCfg = &v3.Config{ + BaseConfig: &config.BaseConfig{Params: &config.Params{CLIName: "ccloud"}}, // TODO: Remove CLIName + Contexts: map[string]*v3.Context{"cloud": { + PlatformName: testserver.TestCloudURL.String(), + Credential: &v2.Credential{CredentialType: v2.Username}, + }}, + CurrentContext: "cloud", + IsTest: true, + } + + onPremCfg = &v3.Config{ + BaseConfig: &config.BaseConfig{Params: &config.Params{CLIName: "confluent"}}, // TODO: Remove CLIName + Contexts: map[string]*v3.Context{"on-prem": { + Credential: new(v2.Credential), + PlatformName: "https://example.com", + State: &v2.ContextState{AuthToken: "token"}, + }}, + CurrentContext: "on-prem", + } + + updatesDisabledCfg = &v3.Config{DisableUpdates: true} + + updatesEnabledCfg = &v3.Config{DisableUpdates: false} +) + +func TestErrIfMissingRunRequirement_NoError(t *testing.T) { + for _, test := range []struct { + req string + cfg *v3.Config + }{ + {RequireCloudLogin, cloudCfg}, + {RequireCloudLoginOrOnPremLogin, cloudCfg}, + {RequireCloudLoginOrOnPremLogin, onPremCfg}, + {RequireNonAPIKeyCloudLogin, nonAPIKeyCloudCfg}, + {RequireNonAPIKeyCloudLoginOrOnPremLogin, nonAPIKeyCloudCfg}, + {RequireNonAPIKeyCloudLoginOrOnPremLogin, onPremCfg}, + {RequireOnPremLogin, onPremCfg}, + {RequireUpdatesEnabled, updatesEnabledCfg}, + } { + cmd := &cobra.Command{Annotations: map[string]string{RunRequirement: test.req}} + err := ErrIfMissingRunRequirement(cmd, test.cfg) + require.NoError(t, err) + } +} + +func TestErrIfMissingRunRequirement_Error(t *testing.T) { + for _, test := range []struct { + req string + cfg *v3.Config + err error + }{ + {RequireCloudLogin, onPremCfg, requireCloudLoginErr}, + {RequireCloudLoginOrOnPremLogin, noContextCfg, requireCloudLoginOrOnPremErr}, + {RequireCloudLoginOrOnPremLogin, noContextCfg, requireCloudLoginOrOnPremErr}, + {RequireNonAPIKeyCloudLogin, apiKeyCloudCfg, requireNonAPIKeyCloudLoginErr}, + {RequireNonAPIKeyCloudLoginOrOnPremLogin, apiKeyCloudCfg, requireNonAPIKeyCloudLoginOrOnPremLoginErr}, + {RequireNonAPIKeyCloudLoginOrOnPremLogin, apiKeyCloudCfg, requireNonAPIKeyCloudLoginOrOnPremLoginErr}, + {RequireOnPremLogin, cloudCfg, requireOnPremLoginErr}, + {RequireUpdatesEnabled, updatesDisabledCfg, requireUpdatesEnabledErr}, + } { + cmd := &cobra.Command{Annotations: map[string]string{RunRequirement: test.req}} + err := ErrIfMissingRunRequirement(cmd, test.cfg) + require.Error(t, err) + require.Equal(t, test.err, err) + } +} + +func TestErrIfMissingRunRequirement_Root(t *testing.T) { + err := ErrIfMissingRunRequirement(&cobra.Command{}, nil) + require.NoError(t, err) +} + +func TestErrIfMissingRunRequirement_Subcommand(t *testing.T) { + a := &cobra.Command{Annotations: map[string]string{RunRequirement: RequireCloudLogin}} + b := &cobra.Command{} + a.AddCommand(b) + + err := ErrIfMissingRunRequirement(b, onPremCfg) + require.Error(t, err) + require.Equal(t, err, requireCloudLoginErr) +} diff --git a/internal/pkg/config/v3/config.go b/internal/pkg/config/v3/config.go index 77e7e9ce54..37116067a7 100644 --- a/internal/pkg/config/v3/config.go +++ b/internal/pkg/config/v3/config.go @@ -92,11 +92,7 @@ func New(params *config.Params) *Config { // Save a default version if none exists yet. func (c *Config) Load() error { currentVersion := Version - filename, err := c.getFilename() - c.Filename = filename - if err != nil { - return err - } + filename := c.GetFilename() input, err := ioutil.ReadFile(filename) if err != nil { if os.IsNotExist(err) { @@ -159,10 +155,7 @@ func (c *Config) Save() error { if err != nil { return errors.Wrapf(err, errors.MarshalConfigErrorMsg) } - filename, err := c.getFilename() - if err != nil { - return err - } + filename := c.GetFilename() err = os.MkdirAll(filepath.Dir(filename), 0700) if err != nil { return errors.Wrapf(err, errors.CreateConfigDirectoryErrorMsg, filename) @@ -381,29 +374,37 @@ func (c *Config) Context() *Context { return c.Contexts[c.CurrentContext] } +// CredentialType returns the credential type used in the current context: API key, username & password, or neither. func (c *Config) CredentialType() v2.CredentialType { - ctx := c.Context() - if ctx == nil { - return v2.None - } - if c.Context().Credential.CredentialType == v2.APIKey { + if c.hasAPIKeyLogin() { return v2.APIKey } - if c.HasLogin() { + + if c.HasBasicLogin() { return v2.Username } + return v2.None } -func (c *Config) HasLogin() bool { +// hasAPIKeyLogin returns true if the user has valid API Key credentials. +func (c *Config) hasAPIKeyLogin() bool { + ctx := c.Context() + return ctx != nil && ctx.Credential != nil && ctx.Credential.CredentialType == v2.APIKey +} + +// HasBasicLogin returns true if the user has valid username & password credentials. +func (c *Config) HasBasicLogin() bool { ctx := c.Context() if ctx == nil { return false } - if c.CLIName == "ccloud" { - return ctx.hasCCloudLogin() + + if c.IsCloudLogin() { + return ctx.hasBasicCloudLogin() + } else { + return ctx.HasBasicMDSLogin() } - return ctx.HasMDSLogin() } func (c *Config) ResetAnonymousId() error { @@ -411,15 +412,15 @@ func (c *Config) ResetAnonymousId() error { return c.Save() } -func (c *Config) getFilename() (string, error) { +func (c *Config) GetFilename() string { if c.Filename == "" { homedir, _ := os.UserHomeDir() c.Filename = filepath.FromSlash(fmt.Sprintf(defaultConfigFileFmt, homedir, "confluent")) } - return c.Filename, nil + return c.Filename } -func (c *Config) IsCloud() bool { +func (c *Config) IsCloudLogin() bool { ctx := c.Context() if ctx == nil { return false @@ -437,11 +438,7 @@ func (c *Config) IsCloud() bool { return false } -func (c *Config) IsOnPrem() bool { +func (c *Config) IsOnPremLogin() bool { ctx := c.Context() - if ctx == nil { - return false - } - - return ctx.PlatformName != "" && !c.IsCloud() + return ctx != nil && ctx.PlatformName != "" && !c.IsCloudLogin() } diff --git a/internal/pkg/config/v3/config_test.go b/internal/pkg/config/v3/config_test.go index 8d323106e4..f1534243a5 100644 --- a/internal/pkg/config/v3/config_test.go +++ b/internal/pkg/config/v3/config_test.go @@ -603,13 +603,9 @@ func TestConfig_getFilename(t *testing.T) { MetricSink: nil, Logger: log.New(), }) - got, err := c.getFilename() - if (err != nil) != tt.wantErr { - t.Errorf("Config.getFilename() error = %v, wantErr %v", err, tt.wantErr) - return - } + got := c.GetFilename() if got != tt.want { - t.Errorf("Config.getFilename() = %v, want %v", got, tt.want) + t.Errorf("Config.GetFilename() = %v, want %v", got, tt.want) } }) } @@ -1075,7 +1071,7 @@ func TestConfig_IsCloud_True(t *testing.T) { Contexts: map[string]*Context{"context": {PlatformName: platform}}, CurrentContext: "context", } - require.True(t, config.IsCloud(), platform+" should be true") + require.True(t, config.IsCloudLogin(), platform+" should be true") } } @@ -1089,7 +1085,7 @@ func TestConfig_IsCloud_False(t *testing.T) { } for _, config := range configs { - require.False(t, config.IsCloud()) + require.False(t, config.IsCloudLogin()) } } @@ -1098,7 +1094,7 @@ func TestConfig_IsOnPrem_True(t *testing.T) { Contexts: map[string]*Context{"context": {PlatformName: "https://example.com"}}, CurrentContext: "context", } - require.True(t, config.IsOnPrem()) + require.True(t, config.IsOnPremLogin()) } func TestConfig_IsOnPrem_False(t *testing.T) { @@ -1115,6 +1111,6 @@ func TestConfig_IsOnPrem_False(t *testing.T) { } for _, config := range configs { - require.False(t, config.IsOnPrem()) + require.False(t, config.IsOnPremLogin()) } } diff --git a/internal/pkg/config/v3/context.go b/internal/pkg/config/v3/context.go index 81f685962c..07344638e9 100644 --- a/internal/pkg/config/v3/context.go +++ b/internal/pkg/config/v3/context.go @@ -116,7 +116,11 @@ func (c *Context) Save() error { return c.Config.Save() } -func (c *Context) HasMDSLogin() bool { +func (c *Context) HasBasicMDSLogin() bool { + if c.Credential == nil { + return false + } + credType := c.Credential.CredentialType switch credType { case v2.Username: @@ -128,7 +132,11 @@ func (c *Context) HasMDSLogin() bool { } } -func (c *Context) hasCCloudLogin() bool { +func (c *Context) hasBasicCloudLogin() bool { + if c.Credential == nil { + return false + } + credType := c.Credential.CredentialType switch credType { case v2.Username: diff --git a/mock/commander.go b/mock/commander.go index cf7fe7256d..1962728893 100644 --- a/mock/commander.go +++ b/mock/commander.go @@ -55,7 +55,7 @@ func NewPreRunnerMdsV2Mock(client *ccloud.Client, mdsClient *mdsv2alpha1.APIClie } } -func (c *Commander) Anonymous(command *pcmd.CLICommand) func(cmd *cobra.Command, args []string) error { +func (c *Commander) Anonymous(command *pcmd.CLICommand, _ bool) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { if command != nil { command.Version = c.Version @@ -68,8 +68,7 @@ func (c *Commander) Anonymous(command *pcmd.CLICommand) func(cmd *cobra.Command, func (c *Commander) Authenticated(command *pcmd.AuthenticatedCLICommand) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - err := c.Anonymous(command.CLICommand)(cmd, args) - if err != nil { + if err := c.Anonymous(command.CLICommand, true)(cmd, args); err != nil { return err } c.setClient(command) @@ -78,18 +77,18 @@ func (c *Commander) Authenticated(command *pcmd.AuthenticatedCLICommand) func(cm return new(errors.NotLoggedInError) } command.Context = ctx - command.State, err = ctx.AuthenticatedState(cmd) + state, err := ctx.AuthenticatedState(cmd) if err != nil { return err } + command.State = state return nil } } func (c *Commander) AuthenticatedWithMDS(command *pcmd.AuthenticatedCLICommand) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - err := c.Anonymous(command.CLICommand)(cmd, args) - if err != nil { + if err := c.Anonymous(command.CLICommand, true)(cmd, args); err != nil { return err } c.setClient(command) @@ -98,7 +97,7 @@ func (c *Commander) AuthenticatedWithMDS(command *pcmd.AuthenticatedCLICommand) return new(errors.NotLoggedInError) } command.Context = ctx - if !ctx.HasMDSLogin() { + if !ctx.HasBasicMDSLogin() { return new(errors.NotLoggedInError) } command.State = ctx.State @@ -108,8 +107,7 @@ func (c *Commander) AuthenticatedWithMDS(command *pcmd.AuthenticatedCLICommand) func (c *Commander) HasAPIKey(command *pcmd.HasAPIKeyCLICommand) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - err := c.Anonymous(command.CLICommand)(cmd, args) - if err != nil { + if err := c.Anonymous(command.CLICommand, true)(cmd, args); err != nil { return err } ctx := command.Config.Context() diff --git a/test/fixtures/output/help/help-cloud-windows.golden b/test/fixtures/output/help/help-cloud-windows.golden index 27bba5e74b..39f16cbe0f 100644 --- a/test/fixtures/output/help/help-cloud-windows.golden +++ b/test/fixtures/output/help/help-cloud-windows.golden @@ -22,7 +22,6 @@ Available Commands: price See Confluent Cloud pricing information. prompt Print Confluent CLI context for your terminal prompt. schema-registry Manage Schema Registry. - secret Manage secrets for Confluent Platform. service-account Manage service accounts. shell Run the confluent shell. update Update the Confluent CLI. diff --git a/test/fixtures/output/help/help-cloud.golden b/test/fixtures/output/help/help-cloud.golden index 739fc84873..4fb33552cf 100644 --- a/test/fixtures/output/help/help-cloud.golden +++ b/test/fixtures/output/help/help-cloud.golden @@ -23,7 +23,6 @@ Available Commands: price See Confluent Cloud pricing information. prompt Print Confluent CLI context for your terminal prompt. schema-registry Manage Schema Registry. - secret Manage secrets for Confluent Platform. service-account Manage service accounts. shell Run the confluent shell. update Update the Confluent CLI. diff --git a/test/fixtures/output/help/help-no-context-windows.golden b/test/fixtures/output/help/help-no-context-windows.golden index 12a76cb81c..e2b4058f09 100644 --- a/test/fixtures/output/help/help-no-context-windows.golden +++ b/test/fixtures/output/help/help-no-context-windows.golden @@ -4,16 +4,15 @@ Usage: confluent [command] Available Commands: - cloud-signup Sign up for Confluent Cloud. - completion Print shell completion code. - config Modify the CLI configuration. - help Help about any command - kafka Manage Apache Kafka. - login Log in to Confluent Cloud or Confluent Platform. - logout Log out of Confluent Cloud or Confluent Platform. - secret Manage secrets for Confluent Platform. - update Update the Confluent CLI. - version Show version of the Confluent CLI. + cloud-signup Sign up for Confluent Cloud. + completion Print shell completion code. + config Modify the CLI configuration. + help Help about any command + kafka Manage Apache Kafka. + login Log in to Confluent Cloud or Confluent Platform. + logout Log out of Confluent Cloud or Confluent Platform. + update Update the Confluent CLI. + version Show version of the Confluent CLI. Flags: -h, --help Show help for this command. diff --git a/test/fixtures/output/help/help-no-context.golden b/test/fixtures/output/help/help-no-context.golden index 69b8fc5b5d..30f5fa3d35 100644 --- a/test/fixtures/output/help/help-no-context.golden +++ b/test/fixtures/output/help/help-no-context.golden @@ -4,17 +4,16 @@ Usage: confluent [command] Available Commands: - cloud-signup Sign up for Confluent Cloud. - completion Print shell completion code. - config Modify the CLI configuration. - help Help about any command - kafka Manage Apache Kafka. - local Manage a local Confluent Platform development environment. - login Log in to Confluent Cloud or Confluent Platform. - logout Log out of Confluent Cloud or Confluent Platform. - secret Manage secrets for Confluent Platform. - update Update the Confluent CLI. - version Show version of the Confluent CLI. + cloud-signup Sign up for Confluent Cloud. + completion Print shell completion code. + config Modify the CLI configuration. + help Help about any command + kafka Manage Apache Kafka. + local Manage a local Confluent Platform development environment. + login Log in to Confluent Cloud or Confluent Platform. + logout Log out of Confluent Cloud or Confluent Platform. + update Update the Confluent CLI. + version Show version of the Confluent CLI. Flags: -h, --help Show help for this command.