From c01cb3f808ba0e5a73ef3e73f221b1b47e0c1477 Mon Sep 17 00:00:00 2001 From: David Adams <187183+davidadas@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:37:57 -0400 Subject: [PATCH 1/5] [APIE-1362] Fix misleading --inclusive flag description on role-binding list The flag's help text said "If scopes are unspecified, list only organization-scoped role bindings," but the command's own documented example ("for all scopes") and actual behavior return role bindings across all scopes (org, environment, cluster) in that case. Corrected the description to match, and updated the 4 golden fixtures that pinned the old text. Co-Authored-By: Claude Sonnet 5 --- internal/iam/command_rbac_role_binding_list.go | 2 +- .../output/iam/rbac/role-binding/list-failure-help-cloud.golden | 2 +- .../iam/rbac/role-binding/list-failure-help-onprem.golden | 2 +- .../output/iam/rbac/role-binding/list-help-onprem.golden | 2 +- test/fixtures/output/iam/rbac/role-binding/list-help.golden | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/iam/command_rbac_role_binding_list.go b/internal/iam/command_rbac_role_binding_list.go index be7ff740e0..1b7653325e 100644 --- a/internal/iam/command_rbac_role_binding_list.go +++ b/internal/iam/command_rbac_role_binding_list.go @@ -114,7 +114,7 @@ func (c *roleBindingCommand) newListCommand() *cobra.Command { } cmd.Flags().String("resource", "", `Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings.`) - cmd.Flags().Bool("inclusive", false, "List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list only organization-scoped role bindings.") + cmd.Flags().Bool("inclusive", false, "List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes.") pcmd.AddOutputFlag(cmd) return cmd diff --git a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden index b3f8a39e1a..1a443dc518 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden @@ -39,7 +39,7 @@ Flags: --ksql-cluster string ksqlDB cluster name, which specifies the ksqlDB cluster scope. --flink-region string Flink region for the role binding, formatted as "cloud.region". --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list only organization-scoped role bindings. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: diff --git a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden index 3297fc0539..260ef7bef3 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden @@ -38,7 +38,7 @@ Flags: --context string CLI context name. --cluster-name string Cluster name, which specifies the cluster scope. --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list only organization-scoped role bindings. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: diff --git a/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden b/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden index e2b2adffb0..3871091301 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden @@ -39,7 +39,7 @@ Flags: --context string CLI context name. --cluster-name string Cluster name, which specifies the cluster scope. --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list only organization-scoped role bindings. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: diff --git a/test/fixtures/output/iam/rbac/role-binding/list-help.golden b/test/fixtures/output/iam/rbac/role-binding/list-help.golden index 28a1de56b8..c18b402070 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-help.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-help.golden @@ -40,7 +40,7 @@ Flags: --ksql-cluster string ksqlDB cluster name, which specifies the ksqlDB cluster scope. --flink-region string Flink region for the role binding, formatted as "cloud.region". --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list only organization-scoped role bindings. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: From 8eb99e26e5d1202926057393ff71b1aa6d5cb794 Mon Sep 17 00:00:00 2001 From: David Adams <187183+davidadas@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:09:21 -0400 Subject: [PATCH 2/5] [APIE-1286] Allow boolean flags to use "=" in examples without failing the linter RequireValidExamples() flagged any --flag=value in an example, but boolean flags legitimately need "=" to set the non-default value (e.g. --flag=false). Added getBoolFlags() and excluded them from the "=" check. Co-Authored-By: Claude Sonnet 5 --- pkg/linter/command_rules.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/linter/command_rules.go b/pkg/linter/command_rules.go index 5a8b84fe96..381e432578 100644 --- a/pkg/linter/command_rules.go +++ b/pkg/linter/command_rules.go @@ -221,6 +221,7 @@ func RequireValidExamples() CommandRule { return func(cmd *cobra.Command) error { requiredFlags := getRequiredFlags(cmd.Flags()) allFlags := getAllFlags(cmd.Flags()) + boolFlags := getBoolFlags(cmd.Flags()) errs := new(multierror.Error) @@ -238,7 +239,12 @@ func RequireValidExamples() CommandRule { } for _, match := range regexp.MustCompile(`--[a-z\-]+=`).FindAllString(example, -1) { - errs = multierror.Append(errs, fmt.Errorf("%s: flag `%s` must not use \"=\" in example %d", cmd.CommandPath(), strings.TrimSuffix(match, "="), i+1)) + flag := strings.TrimSuffix(match, "=") + // Boolean flags legitimately need "=" to set the non-default value, e.g. --flag=false. + if slices.Contains(boolFlags, flag) { + continue + } + errs = multierror.Append(errs, fmt.Errorf("%s: flag `%s` must not use \"=\" in example %d", cmd.CommandPath(), flag, i+1)) } } @@ -274,6 +280,16 @@ func getAllFlags(flags *pflag.FlagSet) []string { return all } +func getBoolFlags(flags *pflag.FlagSet) []string { + var boolFlags []string + flags.VisitAll(func(flag *pflag.Flag) { + if flag.Value.Type() == "bool" { + boolFlags = append(boolFlags, "--"+flag.Name) + } + }) + return boolFlags +} + func getValueByName(obj any, name string) string { return reflect.Indirect(reflect.ValueOf(obj)).FieldByName(name).String() } From bca05bcfc585ffbc791f76872dd77550a05ca4aa Mon Sep 17 00:00:00 2001 From: David Adams <187183+davidadas@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:09:34 -0400 Subject: [PATCH 3/5] [APIE-1314] `confluent logout` should be a no-op when already logged out logout used NewAuthenticatedCLICommand, which required being logged in (erroring "not logged in" with no context) and would auto-login via env-var credentials only to immediately log back out. Switched to NewAnonymousCLICommand (same pattern as `login`) and return immediately when there's no active context, instead of erroring or auto-authenticating. The ccloud client used to revoke the refresh token is now constructed directly in revokeCCloudRefreshToken rather than relying on the Authenticated PreRun to populate it. Co-Authored-By: Claude Sonnet 5 --- internal/logout/command.go | 41 +++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/internal/logout/command.go b/internal/logout/command.go index e30fdd6dec..f6565caad7 100644 --- a/internal/logout/command.go +++ b/internal/logout/command.go @@ -1,6 +1,7 @@ package logout import ( + "context" "fmt" "github.com/spf13/cobra" @@ -12,11 +13,12 @@ import ( "github.com/confluentinc/cli/v4/pkg/ccloudv2" pcmd "github.com/confluentinc/cli/v4/pkg/cmd" "github.com/confluentinc/cli/v4/pkg/config" + "github.com/confluentinc/cli/v4/pkg/log" "github.com/confluentinc/cli/v4/pkg/output" ) type command struct { - *pcmd.AuthenticatedCLICommand + *pcmd.CLICommand cfg *config.Config authTokenHandler pauth.AuthTokenHandler } @@ -28,16 +30,18 @@ func New(cfg *config.Config, prerunner pcmd.PreRunner, authTokenHandler pauth.Au } context := "Confluent Cloud or Confluent Platform" - c := &command{ - AuthenticatedCLICommand: pcmd.NewAuthenticatedCLICommand(cmd, prerunner), - cfg: cfg, - authTokenHandler: authTokenHandler, - } if cfg.IsCloudLogin() { context = "Confluent Cloud" } else if cfg.IsOnPremLogin() { context = "Confluent Platform" - c.AuthenticatedCLICommand = pcmd.NewAuthenticatedWithMDSCLICommand(cmd, prerunner) + } + + c := &command{ + // Anonymous (not Authenticated): logout must not require being logged in, and must not + // trigger an auto-login via env-var credentials only to immediately log back out. + CLICommand: pcmd.NewAnonymousCLICommand(cmd, prerunner), + cfg: cfg, + authTokenHandler: authTokenHandler, } cmd.Short = fmt.Sprintf("Log out of %s.", context) @@ -49,11 +53,14 @@ func New(cfg *config.Config, prerunner pcmd.PreRunner, authTokenHandler pauth.Au func (c *command) logout(_ *cobra.Command, _ []string) error { ctx := c.Config.Context() - if ctx != nil { - if ccloudv2.IsCCloudURL(ctx.Platform.Server, c.cfg.IsTest) { - if _, err := c.revokeCCloudRefreshToken(ctx); err != nil { - return err - } + if ctx == nil { + // Already logged out: do nothing. + return nil + } + + if ccloudv2.IsCCloudURL(ctx.Platform.Server, c.cfg.IsTest) { + if _, err := c.revokeCCloudRefreshToken(ctx); err != nil { + return err } } @@ -71,10 +78,16 @@ func (c *command) revokeCCloudRefreshToken(ctx *config.Context) (*ccloudv1.Authe return nil, err } + client := ccloudv1.NewClientWithJWT(context.Background(), contextState.AuthToken, &ccloudv1.Params{ + BaseURL: ctx.GetPlatformServer(), + Logger: log.CliLogger, + UserAgent: c.Version.UserAgent, + }) + req := &ccloudv1.AuthenticateRequest{IdToken: contextState.AuthToken} if sso.IsOkta(ctx.Platform.Server) { - return c.Client.Auth.OktaLogout(req) + return client.Auth.OktaLogout(req) } else { - return c.Client.Auth.Logout(req) + return client.Auth.Logout(req) } } From e62708183eb03381c22652941637b6718231d8a5 Mon Sep 17 00:00:00 2001 From: David Adams <187183+davidadas@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:22:30 -0400 Subject: [PATCH 4/5] Address Copilot review comment on PR #3421 (APIE-1362) --inclusive is only read by the Cloud-path functions (listMyRoleBindings, ccloudListRolePrincipals), both called exclusively from ccloudList. The on-prem path (confluentList) never reads it, even though the flag is registered unconditionally for both login modes. Clarified the flag description as Confluent Cloud-only so on-prem users don't think it does something. Updated all 4 golden fixtures that pin this text. Co-Authored-By: Claude Sonnet 5 --- internal/iam/command_rbac_role_binding_list.go | 2 +- .../output/iam/rbac/role-binding/list-failure-help-cloud.golden | 2 +- .../iam/rbac/role-binding/list-failure-help-onprem.golden | 2 +- .../output/iam/rbac/role-binding/list-help-onprem.golden | 2 +- test/fixtures/output/iam/rbac/role-binding/list-help.golden | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/iam/command_rbac_role_binding_list.go b/internal/iam/command_rbac_role_binding_list.go index 1b7653325e..7617d1d11b 100644 --- a/internal/iam/command_rbac_role_binding_list.go +++ b/internal/iam/command_rbac_role_binding_list.go @@ -114,7 +114,7 @@ func (c *roleBindingCommand) newListCommand() *cobra.Command { } cmd.Flags().String("resource", "", `Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings.`) - cmd.Flags().Bool("inclusive", false, "List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes.") + cmd.Flags().Bool("inclusive", false, "List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. Only applies to Confluent Cloud.") pcmd.AddOutputFlag(cmd) return cmd diff --git a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden index 1a443dc518..64027504e7 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-cloud.golden @@ -39,7 +39,7 @@ Flags: --ksql-cluster string ksqlDB cluster name, which specifies the ksqlDB cluster scope. --flink-region string Flink region for the role binding, formatted as "cloud.region". --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. Only applies to Confluent Cloud. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: diff --git a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden index 260ef7bef3..990fbebb3f 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-failure-help-onprem.golden @@ -38,7 +38,7 @@ Flags: --context string CLI context name. --cluster-name string Cluster name, which specifies the cluster scope. --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. Only applies to Confluent Cloud. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: diff --git a/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden b/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden index 3871091301..ddcc7284e3 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-help-onprem.golden @@ -39,7 +39,7 @@ Flags: --context string CLI context name. --cluster-name string Cluster name, which specifies the cluster scope. --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. Only applies to Confluent Cloud. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: diff --git a/test/fixtures/output/iam/rbac/role-binding/list-help.golden b/test/fixtures/output/iam/rbac/role-binding/list-help.golden index c18b402070..52febe6b8b 100644 --- a/test/fixtures/output/iam/rbac/role-binding/list-help.golden +++ b/test/fixtures/output/iam/rbac/role-binding/list-help.golden @@ -40,7 +40,7 @@ Flags: --ksql-cluster string ksqlDB cluster name, which specifies the ksqlDB cluster scope. --flink-region string Flink region for the role binding, formatted as "cloud.region". --resource string Resource type and identifier using "Prefix:ID" format. If specified with "--role" and no principals, list all principals and role bindings. - --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. + --inclusive List role bindings for specified scopes and nested scopes. Otherwise, list role bindings for the specified scopes. If scopes are unspecified, list role bindings across all scopes. Only applies to Confluent Cloud. -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") Global Flags: From f396d3f311212287992941dfe15d4247fba9d596 Mon Sep 17 00:00:00 2001 From: David Adams <187183+davidadas@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:20:04 -0400 Subject: [PATCH 5/5] Fix nil pointer panic in logout when Version is unset (APIE-1314) revokeCCloudRefreshToken accessed c.Version.UserAgent directly, but some PreRunner mocks (e.g. internal/login's cross-package test via mock.Commander) never populate Version, causing a nil pointer panic. Confirmed via the full test suite: TestLoginWithExistingContext panicked in internal/login before this fix, passes after it. Guard with a nil check instead of relying on every PreRunner implementation to set Version. Co-Authored-By: Claude Sonnet 5 --- internal/logout/command.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/logout/command.go b/internal/logout/command.go index f6565caad7..f5250d93c2 100644 --- a/internal/logout/command.go +++ b/internal/logout/command.go @@ -78,10 +78,14 @@ func (c *command) revokeCCloudRefreshToken(ctx *config.Context) (*ccloudv1.Authe return nil, err } + var userAgent string + if c.Version != nil { + userAgent = c.Version.UserAgent + } client := ccloudv1.NewClientWithJWT(context.Background(), contextState.AuthToken, &ccloudv1.Params{ BaseURL: ctx.GetPlatformServer(), Logger: log.CliLogger, - UserAgent: c.Version.UserAgent, + UserAgent: userAgent, }) req := &ccloudv1.AuthenticateRequest{IdToken: contextState.AuthToken}