diff --git a/src/go/rpk/pkg/cli/acl/create.go b/src/go/rpk/pkg/cli/acl/create.go index d094595398eb9..7839b5f6ae788 100644 --- a/src/go/rpk/pkg/cli/acl/create.go +++ b/src/go/rpk/pkg/cli/acl/create.go @@ -48,7 +48,7 @@ Allow write permissions to user buzz to transactional id "txn": Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/delete.go b/src/go/rpk/pkg/cli/acl/delete.go index 899bed33debf9..df4e2aca7e961 100644 --- a/src/go/rpk/pkg/cli/acl/delete.go +++ b/src/go/rpk/pkg/cli/acl/delete.go @@ -57,7 +57,7 @@ resource names: Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/list.go b/src/go/rpk/pkg/cli/acl/list.go index 7ff5382e04661..53bbfc415ee4c 100644 --- a/src/go/rpk/pkg/cli/acl/list.go +++ b/src/go/rpk/pkg/cli/acl/list.go @@ -51,7 +51,7 @@ resource names: Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/create.go b/src/go/rpk/pkg/cli/acl/user/create.go index eccecd2a35c56..2ff034e3ed8bc 100644 --- a/src/go/rpk/pkg/cli/acl/user/create.go +++ b/src/go/rpk/pkg/cli/acl/user/create.go @@ -56,7 +56,7 @@ acl help text for more info. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/delete.go b/src/go/rpk/pkg/cli/acl/user/delete.go index 03d3efe7d9da7..163d5561d3718 100644 --- a/src/go/rpk/pkg/cli/acl/user/delete.go +++ b/src/go/rpk/pkg/cli/acl/user/delete.go @@ -36,7 +36,7 @@ delete any ACLs that may exist for this user. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/list.go b/src/go/rpk/pkg/cli/acl/user/list.go index 7040cddfa6eba..9c0469c50e5a4 100644 --- a/src/go/rpk/pkg/cli/acl/user/list.go +++ b/src/go/rpk/pkg/cli/acl/user/list.go @@ -28,7 +28,7 @@ func newListUsersCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/update.go b/src/go/rpk/pkg/cli/acl/user/update.go index c12befeca1bf2..a2db9af26eeb4 100644 --- a/src/go/rpk/pkg/cli/acl/user/update.go +++ b/src/go/rpk/pkg/cli/acl/user/update.go @@ -28,7 +28,7 @@ func newUpdateCommand(fs afero.Fs, p *config.Params) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { f := p.Formatter p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cloud/auth/auth.go b/src/go/rpk/pkg/cli/cloud/auth/auth.go index bfd8c6fd9cc02..7158db10b237c 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/auth.go +++ b/src/go/rpk/pkg/cli/cloud/auth/auth.go @@ -64,3 +64,13 @@ func validAuths(fs afero.Fs, p *config.Params) func(*cobra.Command, []string, st return names, cobra.ShellCompDirectiveDefault } } + +func findName(y *config.RpkYaml, name string) map[int]struct{} { + nameMatches := make(map[int]struct{}) + for i, a := range y.CloudAuths { + if a.Name == name { + nameMatches[i] = struct{}{} + } + } + return nameMatches +} diff --git a/src/go/rpk/pkg/cli/cloud/auth/create.go b/src/go/rpk/pkg/cli/cloud/auth/create.go index 8f517d09a864f..966b86f65fc73 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/create.go +++ b/src/go/rpk/pkg/cli/cloud/auth/create.go @@ -11,94 +11,23 @@ package auth import ( "fmt" - "reflect" - "strings" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" "github.com/spf13/afero" "github.com/spf13/cobra" ) func newCreateCommand(fs afero.Fs, p *config.Params) *cobra.Command { - var ( - set []string - description string - ) cmd := &cobra.Command{ - Use: "create [NAME]", - Short: "Create an rpk cloud auth", - Long: `Create an rpk cloud auth. - -This command creates a new rpk cloud auth. The default SSO login does not need -any additional auth values (the token is populated on login) so you can just -create an empty auth. You can also use --set to set key=value pairs for client -credentials. - -The --set flag supports autocompletion, suggesting the -X key format. If you -begin writing a YAML path, the flag will suggest the rest of the path. - -rpk always switches the current cloud auth to the newly created auth. -`, - Args: cobra.ExactArgs(1), - Run: func(_ *cobra.Command, args []string) { - cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) - - yAct, err := cfg.ActualRpkYamlOrEmpty() - out.MaybeDie(err, "unable to load rpk.yaml: %v", err) - - name := args[0] - if a := yAct.Auth(name); a != nil { - out.Die("cloud auth %q already exists", name) - } - - var a config.RpkCloudAuth - for _, kv := range set { - split := strings.SplitN(kv, "=", 2) - if len(split) != 2 { - out.Die("invalid key=value pair %q", kv) - } - err := config.Set(&a, split[0], split[1]) - out.MaybeDieErr(err) - } - a.Name = name - a.Description = description - - if a.ClientID != "" || a.ClientSecret != "" { - if a.ClientID == "" || a.ClientSecret == "" { - out.Die("client-id and client-secret must both be set or both be empty") - } - } - - yAct.CurrentCloudAuth = yAct.PushAuth(a) - err = yAct.Write(fs) - out.MaybeDie(err, "unable to write rpk.yaml: %v", err) - fmt.Printf("Created and switched to new cloud auth %q.\n", name) + Use: "create [NAME]", + Short: "Create an rpk cloud auth", + Hidden: true, + Run: func(*cobra.Command, []string) { + fmt.Println("'rpk cloud auth create' is deprecated as a no-op; use 'rpk cloud login --new-credentials' instead.") }, } - cmd.Flags().StringSliceVarP(&set, "set", "s", nil, "A key=value pair to set in the cloud auth") - cmd.Flags().StringVar(&description, "description", "", "Optional description of the auth") - cmd.RegisterFlagCompletionFunc("set", createSetCompletion) + var slice []string + cmd.Flags().StringSliceVarP(&slice, "set", "s", nil, "A key=value pair to set in the cloud auth") + cmd.Flags().StringVar(new(string), "description", "", "Optional description of the auth") return cmd } - -func createSetCompletion(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { - var possibilities []string - t := reflect.TypeOf(config.RpkCloudAuth{}) - for i := 0; i < t.NumField(); i++ { - sf := t.Field(i) - tag := sf.Tag.Get("yaml") - if comma := strings.IndexByte(tag, ','); comma != -1 { - tag = tag[:comma] - } - if tag == "name" || tag == "description" { - // name is an arg, description is a flag: do not autocomplete these - continue - } - if strings.HasPrefix(tag, toComplete) { - possibilities = append(possibilities, tag+"=") - } - } - return possibilities, cobra.ShellCompDirectiveNoSpace -} diff --git a/src/go/rpk/pkg/cli/cloud/auth/delete.go b/src/go/rpk/pkg/cli/cloud/auth/delete.go index a426064df0fb4..1930a6fc0a1ed 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/delete.go +++ b/src/go/rpk/pkg/cli/cloud/auth/delete.go @@ -11,6 +11,7 @@ package auth import ( "fmt" + "sort" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" @@ -19,20 +20,24 @@ import ( ) func newDeleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { - return &cobra.Command{ - Use: "delete [NAME]", + cmd := &cobra.Command{ + Use: "delete [NAME] --org [ORG]", Short: "Delete an rpk cloud auth", Long: `Delete an rpk cloud auth. Deleting a cloud auth removes it from the rpk.yaml file. If the deleted auth was the current auth, rpk will use a default SSO auth the next time -you try to login and, if the login is successful, will safe that auth. +you try to login and save that auth. + +If you delete an auth that is used by profiles, affected profiles have +their auth cleared and you will only be able to access the profile's +cluster via SASL credentials. `, Args: cobra.ExactArgs(1), ValidArgsFunction: validAuths(fs, p), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { @@ -40,38 +45,79 @@ you try to login and, if the login is successful, will safe that auth. } name := args[0] - wasUsing, err := deleteAuth(fs, y, name) - out.MaybeDieErr(err) + + nameMatches := findName(y, name) + if len(nameMatches) == 0 { + out.Die("cloud auth %q does not exist", name) + } + + // We could have deleted multiple if there is a bug in + // the logic or if the file is corrupted somehow; we do + // exact name and org match and should prevent creation + // of duplicates. But, if we delete multiple, that's + // ok; we just print information about the first. + keep := y.CloudAuths[:0] + var deleted config.RpkCloudAuth + for i := range y.CloudAuths { + a := y.CloudAuths[i] + if _, ok := nameMatches[i]; ok { + deleted = a + } else { + keep = append(keep, a) + } + } + y.CloudAuths = keep + + // Gather all profiles containing this auth and + // prompt confirm if the user is ok with deleting + // auth attached to profiles. + attachedProfiles := make(map[int]struct{}) + for i, p := range y.Profiles { + if p.CloudCluster.HasAuth(deleted) { + attachedProfiles[i] = struct{}{} + } + } + + if len(attachedProfiles) > 0 { + names := make([]string, 0, len(attachedProfiles)) + for i := range attachedProfiles { + names = append(names, y.Profiles[i].Name) + } + sort.Strings(names) + fmt.Println("The following profiles are currently using this cloud auth:") + for _, name := range names { + fmt.Printf(" %s\n", name) + } + fmt.Println("Deleting this auth will mean the profiles no longer can talk to the cloud.") + fmt.Println("The profiles will only be able to talk to the cluster via SASL credentials,") + fmt.Println("which you must set in the profile manually via 'rpk profile edit'.") + c, err := out.Confirm("Do you still want to delete this auth and remove it from the affected profiles?") + out.MaybeDie(err, "unable to confirm deletion: %v", err) + if !c { + fmt.Println("Deletion canceled.") + return + } + for i := range attachedProfiles { + p := &y.Profiles[i] + p.CloudCluster.AuthOrgID = "" + p.CloudCluster.AuthKind = "" + } + } + + err = y.Write(fs) + out.MaybeDie(err, "unable to write rpk.yaml: %v", err) + + wasUsing := y.CurrentCloudAuthOrgID == deleted.OrgID && y.CurrentCloudAuthKind == string(deleted.AnyKind()) + fmt.Printf("Deleted cloud auth %q.\n", name) if wasUsing { - fmt.Println("The current profile was using this cloud auth.\nYou may need to reauthenticate before the current profile can be used again.") + fmt.Print(`This was the current auth selected. + +You may need to reauthenticate with 'rpk cloud login' or swap to a different +profile that uses different auth using cloud API commands again. +`) } }, } -} - -func deleteAuth( - fs afero.Fs, - y *config.RpkYaml, - name string, -) (wasUsing bool, err error) { - idx := -1 - for i, a := range y.CloudAuths { - if a.Name == name { - idx = i - break - } - } - if idx == -1 { - return false, fmt.Errorf("cloud auth %q does not exist", name) - } - y.CloudAuths = append(y.CloudAuths[:idx], y.CloudAuths[idx+1:]...) - ca := y.Auth(y.CurrentCloudAuth) - if wasUsing = ca != nil && ca.Name == name; wasUsing { - y.CurrentCloudAuth = "" - } - if err := y.Write(fs); err != nil { - return false, fmt.Errorf("unable to write rpk file: %v", err) - } - return wasUsing, nil + return cmd } diff --git a/src/go/rpk/pkg/cli/cloud/auth/edit.go b/src/go/rpk/pkg/cli/cloud/auth/edit.go index 11a1a506bcf14..c43a75f9dd7c9 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/edit.go +++ b/src/go/rpk/pkg/cli/cloud/auth/edit.go @@ -13,63 +13,20 @@ import ( "fmt" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" - rpkos "github.com/redpanda-data/redpanda/src/go/rpk/pkg/os" - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" "github.com/spf13/afero" "github.com/spf13/cobra" ) func newEditCommand(fs afero.Fs, p *config.Params) *cobra.Command { - return &cobra.Command{ - Use: "edit [NAME]", - Short: "Edit an rpk auth", - Long: `Edit an rpk auth. - -This command opens your default editor to edit the specified cloud auth, or the -current cloud auth if no cloud auth is specified. -`, + cmd := &cobra.Command{ + Use: "edit [NAME]", + Short: "Edit an rpk auth", Args: cobra.MaximumNArgs(1), ValidArgsFunction: validAuths(fs, p), - Run: func(_ *cobra.Command, args []string) { - cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) - - y, err := cfg.ActualRpkYamlOrEmpty() - out.MaybeDieErr(err) - if len(args) == 0 { - args = append(args, y.CurrentCloudAuth) - } - name := args[0] - a := y.Auth(name) - if a == nil { - out.Die("auth %s does not exist", name) - return - } - - update, err := rpkos.EditTmpYAMLFile(fs, *a) - out.MaybeDieErr(err) - - var renamed, updatedCurrent bool - if update.Name != name { - renamed = true - if y.CurrentCloudAuth == name { - updatedCurrent = true - y.CurrentCloudAuth = update.Name - } - } - *a = update - - err = y.Write(fs) - out.MaybeDie(err, "unable to write rpk.yaml: %v", err) - - if renamed { - fmt.Printf("Cloud auth %q updated successfully and renamed to %q.\n", name, a.Name) - if updatedCurrent { - fmt.Printf("Current cloud auth has been updated to %q.\n", a.Name) - } - } else { - fmt.Printf("Cloud auth %q updated successfully.\n", name) - } + Run: func(*cobra.Command, []string) { + fmt.Println("edit is deprecated, rpk now fully manages auth fields.") }, } + cmd.Flags().StringVarP(new(string), "org", "o", "", "The organization of the cloud auth to edit") + return cmd } diff --git a/src/go/rpk/pkg/cli/cloud/auth/list.go b/src/go/rpk/pkg/cli/cloud/auth/list.go index 9a85ab674054e..102f80971dd0c 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/list.go +++ b/src/go/rpk/pkg/cli/cloud/auth/list.go @@ -26,9 +26,9 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(0), Run: func(*cobra.Command, []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) - tw := out.NewTable("name", "kind", "description") + tw := out.NewTable("name", "kind", "organization", "organization-id") defer tw.Flush() y, ok := cfg.ActualRpkYaml() @@ -37,21 +37,21 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { } sort.Slice(y.CloudAuths, func(i, j int) bool { - return y.CloudAuths[i].Name < y.CloudAuths[j].Name + // First by organization name, then by org ID, then by name. + l, r := y.CloudAuths[i], y.CloudAuths[j] + return l.Organization < r.Organization || + (l.Organization == r.Organization && (l.OrgID < r.OrgID || + (l.OrgID == r.OrgID && l.Name < r.Name))) }) - var current *string - if ca := y.Auth(y.CurrentCloudAuth); ca != nil { - current = &ca.Name - } for i := range y.CloudAuths { a := &y.CloudAuths[i] name := a.Name - if current != nil && name == *current { + if a.OrgID == y.CurrentCloudAuthOrgID && string(a.AnyKind()) == y.CurrentCloudAuthKind { name += "*" } kind, _ := a.Kind() - tw.Print(name, kind, a.Description) + tw.Print(name, kind, a.Organization, a.OrgID) } }, } diff --git a/src/go/rpk/pkg/cli/cloud/auth/rename.go b/src/go/rpk/pkg/cli/cloud/auth/rename.go index 60099818182d6..cecc486dd250f 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/rename.go +++ b/src/go/rpk/pkg/cli/cloud/auth/rename.go @@ -13,7 +13,6 @@ import ( "fmt" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -23,31 +22,9 @@ func newRenameToCommand(fs afero.Fs, p *config.Params) *cobra.Command { Use: "rename-to [NAME]", Short: "Rename the current rpk auth", Aliases: []string{"rename"}, - Args: cobra.ExactArgs(1), - Run: func(_ *cobra.Command, args []string) { - cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) - - y, ok := cfg.ActualRpkYaml() - if !ok { - out.Die("rpk.yaml file does not exist") - } - - p := y.Auth(y.CurrentCloudAuth) - if p == nil { - out.Die("current auth %q does not exist", y.CurrentCloudAuth) - return - } - to := args[0] - if y.Auth(to) != nil { - out.Die("destination cloud auth %q already exists", to) - } - p.Name = to - y.CurrentCloudAuth = to - y.MoveAuthToFront(p) - err = y.Write(fs) - out.MaybeDieErr(err) - fmt.Printf("Renamed current cloud auth to %q.\n", to) + Hidden: true, + Run: func(*cobra.Command, []string) { + fmt.Println("rename-to is deprecated, rpk now fully manages auth names.") }, } } diff --git a/src/go/rpk/pkg/cli/cloud/auth/use.go b/src/go/rpk/pkg/cli/cloud/auth/use.go index a0156b2d873e0..2547a1aa41381 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/use.go +++ b/src/go/rpk/pkg/cli/cloud/auth/use.go @@ -20,13 +20,21 @@ import ( func newUseCommand(fs afero.Fs, p *config.Params) *cobra.Command { cmd := &cobra.Command{ - Use: "use [NAME]", - Short: "Select the rpk cloud auth to use", + Use: "use [NAME]", + Short: "Select the rpk cloud auth to use", + Long: `Select the rpk cloud auth to use. + +This swaps the current cloud auth to the specified cloud auth. If your current +profile is a cloud profile, this unsets the current profile (since the auth +is now different). If your current profile is for a self hosted cluster, the +profile is kept. +`, + Args: cobra.ExactArgs(1), ValidArgsFunction: validAuths(fs, p), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { @@ -34,18 +42,39 @@ func newUseCommand(fs afero.Fs, p *config.Params) *cobra.Command { } name := args[0] - a := y.Auth(name) - if a == nil { - out.Die("auth %q does not exist", name) + nameMatches := findName(y, name) + if len(nameMatches) > 1 { + out.Die("multiple cloud auths with name %q exist, something went wrong in the underlying rpk.yaml file; please delete this auth", name) + } else if len(nameMatches) == 0 { + out.Die("cloud auth %q does not exist", name) } - y.CurrentCloudAuth = name - y.MoveAuthToFront(a) + var idx int + for i := range nameMatches { + idx = i + } + a := &y.CloudAuths[idx] + + var profileCleared bool + var priorProfile string + p := y.Profile(y.CurrentProfile) + if p != nil { + if !p.CloudCluster.HasAuth(*a) { + priorProfile = y.CurrentProfile + y.CurrentProfile = "" + profileCleared = true + } + } + + y.MakeAuthCurrent(a) err = y.Write(fs) - out.MaybeDieErr(err) + out.MaybeDie(err, "unable to write rpk.yaml: %v", err) + + if profileCleared { + fmt.Printf("Clearing the current profile %q which is using the auth that is being swapped swapped from.\n", priorProfile) + } fmt.Printf("Set current cloud auth to %q.\n", name) }, } - return cmd } diff --git a/src/go/rpk/pkg/cli/cloud/byoc/install.go b/src/go/rpk/pkg/cli/cloud/byoc/install.go index 0ff438ec55ccf..61be2063a2921 100644 --- a/src/go/rpk/pkg/cli/cloud/byoc/install.go +++ b/src/go/rpk/pkg/cli/cloud/byoc/install.go @@ -43,7 +43,7 @@ exists if you want to download the plugin ahead of time. Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) _, _, installed, err := loginAndEnsurePluginVersion(cmd.Context(), fs, cfg, redpandaID, false) // latest is always false, we only want to install the pinned byoc version when using `rpk cloud byoc install` out.MaybeDie(err, "unable to install byoc plugin: %v", err) if !installed { @@ -75,10 +75,13 @@ func loginAndEnsurePluginVersion(ctx context.Context, fs afero.Fs, cfg *config.C if overrides.CloudToken != "" { token = overrides.CloudToken } else { - token, err = oauth.LoadFlow(ctx, fs, cfg, auth0.NewClient(overrides), false) + priorProfile := cfg.ActualProfile() + _, authVir, clearedProfile, _, err := oauth.LoadFlow(ctx, fs, cfg, auth0.NewClient(cfg.DevOverrides()), false, false, cfg.DevOverrides().CloudAPIURL) if err != nil { return "", "", false, fmt.Errorf("unable to load the cloud token: %w. You may need to logout with 'rpk cloud logout --clear-credentials' and try again", err) } + oauth.MaybePrintSwapMessage(clearedProfile, priorProfile, authVir) + token = authVir.AuthToken } byoc, pluginExists := plugin.ListPlugins(fs, plugin.UserPaths()).Find("byoc") diff --git a/src/go/rpk/pkg/cli/cloud/login.go b/src/go/rpk/pkg/cli/cloud/login.go index 90fa0ec152c2c..7e00230aa4285 100644 --- a/src/go/rpk/pkg/cli/cloud/login.go +++ b/src/go/rpk/pkg/cli/cloud/login.go @@ -10,17 +10,12 @@ package cloud import ( - "context" "errors" "fmt" "os" - "sort" - "time" - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/profile" - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cloudapi" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/container/common" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/httpapi" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" @@ -29,7 +24,7 @@ import ( ) func newLoginCommand(fs afero.Fs, p *config.Params) *cobra.Command { - var save, noProfile, noBrowser bool + var save, noProfile, noBrowser, forceReload bool cmd := &cobra.Command{ Use: "login", Short: "Log in to the Redpanda cloud", @@ -43,6 +38,11 @@ request the token. You may use either SSO or client credentials to log in. +Logging in uses an existing current token if the token is still valid and not +expired. If you switched organizations in your browser and want to log into the +new organization, you can use the --reload flag to force a new token to be +requested. + SSO This will automatically launch your default web browser and prompt you to @@ -65,29 +65,18 @@ If none of these are provided, rpk will use the SSO method to login. If you specify environment variables or flags, they will not be synced to the rpk.yaml file unless the --save flag is passed. The cloud authorization token and client ID is always synced. - -PROFILE SELECTION - -This command by default attempts to populate a new profile that talks to a -cloud cluster for you. If you have an existing cloud profile, this will select -it, prompting which to use if you have many. If you have no cloud profile, this -command will prompt you to select one that exists in your organization. If you -want to disable automatic profile creation and selection, use --no-profile. `, Run: func(cmd *cobra.Command, _ []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, err := cfg.ActualRpkYamlOrEmpty() - out.MaybeDie(err, "unable to load config: %v", err) - auth := y.Auth(y.CurrentCloudAuth) - var cc bool - if auth != nil { - cc = auth.HasClientCredentials() - } - _, err = oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), noBrowser) + out.MaybeDie(err, "rpk unable to load config: %v", err) + + p := y.Profile(y.CurrentProfile) + authAct, authVir, clearedProfile, isNewAuth, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), noBrowser, forceReload, cfg.DevOverrides().CloudAPIURL) if err != nil { fmt.Printf("Unable to login to Redpanda Cloud (%v).\n", err) - if e := (*oauth.BadClientTokenError)(nil); errors.As(err, &e) && cc { + if e := (*oauth.BadClientTokenError)(nil); errors.As(err, &e) && authVir != nil && authVir.HasClientCredentials() { fmt.Println(`You may need to clear your client ID and secret with 'rpk cloud logout --clear-credentials', and then re-specify the client credentials next time you log in.`) } else { @@ -95,123 +84,72 @@ and then re-specify the client credentials next time you log in.`) } os.Exit(1) } - - if cc && save { - yAct, _ := cfg.ActualRpkYaml() // must exist due to LoadFlow checking - yAct.Auth(yAct.CurrentCloudAuth).ClientSecret = auth.ClientSecret - err = yAct.Write(fs) + if authVir.HasClientCredentials() && save { + authAct.ClientSecret = authVir.ClientSecret + err = y.Write(fs) out.MaybeDie(err, "unable to save client ID and client secret: %v", err) } - fmt.Println("Successfully logged in.") - if noProfile { + + fmt.Printf("Successfully logged into organization %q (%s) via %s.\n", authAct.Organization, authAct.OrgID, authAct.AnyKind()) + if !isNewAuth { + fmt.Println("If you are trying to authenticate to a different organization, use 'rpk cloud login --reload'.") + } + fmt.Println() + + // When you have no profile, or you have one that is not selected. + if p == nil || len(y.Profiles) == 0 { + fmt.Println(`To create an rpk profile to talk to an existing cloud cluster, use 'rpk cloud cluster use'. +To learn more about profiles, check 'rpk profile --help'. +You are not currently in a profile; rpk talks to a localhost:9092 cluster by default.`) + return + } + + // When you had a profile, but it was cleared due to your browser + // having a different org's auth. + if p != nil && clearedProfile { + priorAuth := p.ActualAuth() + fmt.Printf(`rpk swapped away from your prior profile %q because that profile authenticates +with organization %q (%s). + +To create a new rpk profile for a cluster in this organization, try either: + rpk profile create --from-cloud + rpk cloud cluster use + +rpk will talk to a localhost:9092 cluster until you swap to a different profile. +`, p.Name, priorAuth.Organization, priorAuth.OrgID) return } - msg, err := loginProfileFlow(cmd.Context(), fs, cfg, cfg.DevOverrides().CloudAPIURL) - if err != nil { - fmt.Printf("Unable to create and switch to profile: %v\n", err) - fmt.Printf("Once any error is fixed, you can create a profile with\n") - fmt.Printf(" rpk profile create {name} --from-cloud {cluster_id}\n") + // When your current profile is a cloud cluster. + if p.FromCloud { + fmt.Printf("Your current profile %q is talking to your %q cloud cluster.\n", p.Name, p.CloudCluster.FullName()) + fmt.Println("To switch which cluster you are talking to, use 'rpk cloud cluster use'.") + return + } + + // When your current profile is pointing at the + // localhost cluster from rpk container start. + if p.Name == common.ContainerProfileName { + fmt.Printf("Your current profile %q is talking to a localhost 'rpk container' cluster.\n", p.Name) + fmt.Println("To talk to a cloud cluster, use 'rpk cloud cluster use'.") return } - fmt.Println(msg) + + // When your current profile is a self hosted cluster. + fmt.Printf("Your current profile %q is talking to a self hosted cluster.\n", p.Name) + fmt.Println("To talk to a cloud cluster, use 'rpk cloud cluster use'.") }, } p.InstallCloudFlags(cmd) - cmd.Flags().BoolVar(&noProfile, "no-profile", false, "Skip automatic profile creation and any associated prompts") cmd.Flags().BoolVar(&noBrowser, "no-browser", false, "Opt out of auto-opening authentication URL") cmd.Flags().BoolVar(&save, "save", false, "Save environment or flag specified client ID and client secret to the configuration file") - return cmd -} + cmd.Flags().BoolVar(&forceReload, "reload", false, "Force a new token to be requested, even if the current token is still valid") -func loginProfileFlow(ctx context.Context, fs afero.Fs, cfg *config.Config, overrideCloudURL string) (string, error) { - y, err := cfg.ActualRpkYamlOrEmpty() - if err != nil { - return "", fmt.Errorf("unable to load config: %v", err) - } - auth := y.Auth(y.CurrentCloudAuth) - // If our current profile is a cloud cluster, we exit. - // If one cloud profile exists, we switch to it. - // If two+ cloud profiles exist, we prompt the user to select one. - if p := y.Profile(y.CurrentProfile); p != nil && p.FromCloud { - return "", nil - } - var cloudProfiles []*config.RpkProfile - for i := range y.Profiles { - p := &y.Profiles[i] - if p.FromCloud { - cloudProfiles = append(cloudProfiles, p) - } - } - if len(cloudProfiles) == 1 { - p := cloudProfiles[0] - y.MoveProfileToFront(p) - y.CurrentProfile = p.Name - return fmt.Sprintf("Set current profile to %q.", p.Name), y.Write(fs) - } else if len(cloudProfiles) > 1 { - var names []string - for _, p := range cloudProfiles { - names = append(names, p.Name) - } - sort.Strings(names) - name, err := out.Pick(names, "Which cloud profile would you like to switch to?") - if err != nil { - return "", err - } - for _, p := range cloudProfiles { - if p.Name == name { - y.MoveProfileToFront(p) - y.CurrentProfile = p.Name - return fmt.Sprintf("Set current profile to %q.", p.Name), y.Write(fs) - } - } - panic("unreachable") - } - - // Zero cloud profiles exist. We automatically create one from any - // existing cloud cluster. - // * One cluster exists: create profile for it and swap, no prompt. - // * Multiple clusters exist: prompt which to choose and swap. - cl := cloudapi.NewClient(overrideCloudURL, auth.AuthToken, httpapi.ReqTimeout(10*time.Second)) - - pres, err := profile.PromptCloudClusterProfile(ctx, cl) - if err != nil { - if errors.Is(err, profile.ErrNoCloudClusters) { - return `You currently have no cloud clusters, when you create one you can run -'rpk profile create --from-cloud' to create a profile for it.`, nil - } - return "", err - } + // Hide the deprecated no-profile flag. + // We used to automatically create a profile. + cmd.Flags().BoolVar(&noProfile, "no-profile", false, "Skip automatic profile creation and any associated prompts") + _ = cmd.Flags().MarkHidden("no-profile") - // Before pushing this profile, we first check if the name exists. If - // so, we prompt. - p := pres.Profile - name := p.Name - for { - if y.Profile(name) == nil { - break - } - p.Name, err = out.Prompt("Profile %q already exists, what would you like to name this new profile?", name) - if err != nil { - return "", err - } - } - y.CurrentProfile = y.PushProfile(p) - - // We always print the cloud cluster message first, and then optionally - // print a few extra things. Serverless never has MTLS nor SASL - // messages, we don't need to worry about ordering much. - msg := fmt.Sprintf("Created profile %q and set it as the current profile.\n", p.Name) - msg += profile.CloudClusterMessage(p, pres.ClusterName, pres.ClusterID) - if pres.MessageMTLS { - msg += profile.RequiresMTLSMessage() - } - if pres.MessageSASL { - msg += profile.RequiresSASLMessage() - } - if pres.IsServerlessHello { - msg += profile.ServerlessHelloMessage() - } - return msg, y.Write(fs) + return cmd } diff --git a/src/go/rpk/pkg/cli/cloud/logout.go b/src/go/rpk/pkg/cli/cloud/logout.go index fc5aa203bdcaa..b92802caa5ead 100644 --- a/src/go/rpk/pkg/cli/cloud/logout.go +++ b/src/go/rpk/pkg/cli/cloud/logout.go @@ -36,14 +36,14 @@ additionally clear your client ID and client secret. out.Die("detected rpk is running with sudo; please execute this command without sudo to avoid saving the cloud configuration as a root owned file") } cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { fmt.Println("You are not logged in.") return } - a := y.Auth(y.CurrentCloudAuth) + a := y.CurrentAuth() if a == nil || a.AuthToken == "" && !clear { fmt.Println("You are not logged in.") return diff --git a/src/go/rpk/pkg/cli/cloud/namespace/create.go b/src/go/rpk/pkg/cli/cloud/namespace/create.go index 533603ddcdfc9..9737a21114d10 100644 --- a/src/go/rpk/pkg/cli/cloud/namespace/create.go +++ b/src/go/rpk/pkg/cli/cloud/namespace/create.go @@ -40,9 +40,12 @@ func createCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) - authToken, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false) + out.MaybeDie(err, "rpk unable to load config: %v", err) + priorProfile := cfg.ActualProfile() + _, authVir, clearedProfile, _, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false, false, cfg.DevOverrides().CloudAPIURL) out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err) + oauth.MaybePrintSwapMessage(clearedProfile, priorProfile, authVir) + authToken := authVir.AuthToken cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken) out.MaybeDie(err, "unable to create the public api client: %v", err) diff --git a/src/go/rpk/pkg/cli/cloud/namespace/delete.go b/src/go/rpk/pkg/cli/cloud/namespace/delete.go index 8df6390d5f58f..c0fdec1670b8c 100644 --- a/src/go/rpk/pkg/cli/cloud/namespace/delete.go +++ b/src/go/rpk/pkg/cli/cloud/namespace/delete.go @@ -30,9 +30,12 @@ func deleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) - authToken, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false) + out.MaybeDie(err, "rpk unable to load config: %v", err) + priorProfile := cfg.ActualProfile() + _, authVir, clearedProfile, _, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false, false, cfg.DevOverrides().CloudAPIURL) out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err) + oauth.MaybePrintSwapMessage(clearedProfile, priorProfile, authVir) + authToken := authVir.AuthToken cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken) out.MaybeDie(err, "unable to create the public api client: %v", err) diff --git a/src/go/rpk/pkg/cli/cloud/namespace/list.go b/src/go/rpk/pkg/cli/cloud/namespace/list.go index 0ef3daef444b5..5ccd68f660fc5 100644 --- a/src/go/rpk/pkg/cli/cloud/namespace/list.go +++ b/src/go/rpk/pkg/cli/cloud/namespace/list.go @@ -31,9 +31,12 @@ func listCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) - authToken, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false) + out.MaybeDie(err, "rpk unable to load config: %v", err) + priorProfile := cfg.ActualProfile() + _, authVir, clearedProfile, _, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false, false, cfg.DevOverrides().CloudAPIURL) out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err) + oauth.MaybePrintSwapMessage(clearedProfile, priorProfile, authVir) + authToken := authVir.AuthToken cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken) out.MaybeDie(err, "unable to create the public api client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/edit.go b/src/go/rpk/pkg/cli/cluster/config/edit.go index 6f7d07bb6740e..ead231a6d3305 100644 --- a/src/go/rpk/pkg/cli/cluster/config/edit.go +++ b/src/go/rpk/pkg/cli/cluster/config/edit.go @@ -44,8 +44,8 @@ to edit all properties including these tunables. Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/export.go b/src/go/rpk/pkg/cli/cluster/config/export.go index ab8d11c25eba6..ffd6493dc9ebd 100644 --- a/src/go/rpk/pkg/cli/cluster/config/export.go +++ b/src/go/rpk/pkg/cli/cluster/config/export.go @@ -144,8 +144,8 @@ to include all properties including these low level tunables. `, Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/get.go b/src/go/rpk/pkg/cli/cluster/config/get.go index 07a061a4dd14c..fbfa03121a111 100644 --- a/src/go/rpk/pkg/cli/cluster/config/get.go +++ b/src/go/rpk/pkg/cli/cluster/config/get.go @@ -33,8 +33,8 @@ output, use the 'edit' and 'export' commands respectively.`, key := args[0] p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/import.go b/src/go/rpk/pkg/cli/cluster/config/import.go index cccf242a6e6e1..07ae94f2d94f2 100644 --- a/src/go/rpk/pkg/cli/cluster/config/import.go +++ b/src/go/rpk/pkg/cli/cluster/config/import.go @@ -294,8 +294,8 @@ updates any properties that were changed. If a property is removed from the YAML file, it is reset to its default value. `, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/lint.go b/src/go/rpk/pkg/cli/cluster/config/lint.go index 537430d72b0c9..b6f1233408583 100644 --- a/src/go/rpk/pkg/cli/cluster/config/lint.go +++ b/src/go/rpk/pkg/cli/cluster/config/lint.go @@ -53,9 +53,9 @@ central configuration store (and via 'rpk cluster config edit'). `, Run: func(cmd *cobra.Command, propertyNames []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) p := cfg.VirtualProfile() - out.CheckExitCloudAdmin(p) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/reset.go b/src/go/rpk/pkg/cli/cluster/config/reset.go index b37d3957326ef..1336c462d1ffe 100644 --- a/src/go/rpk/pkg/cli/cluster/config/reset.go +++ b/src/go/rpk/pkg/cli/cluster/config/reset.go @@ -43,7 +43,7 @@ WARNING: this should only be used when redpanda is not running. Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, propertyNames []string) { y, err := p.LoadVirtualRedpandaYaml(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) dataDir := y.Redpanda.Directory diff --git a/src/go/rpk/pkg/cli/cluster/config/set.go b/src/go/rpk/pkg/cli/cluster/config/set.go index 19cf4e4bf8d3a..c00c22c44aa5e 100644 --- a/src/go/rpk/pkg/cli/cluster/config/set.go +++ b/src/go/rpk/pkg/cli/cluster/config/set.go @@ -70,8 +70,8 @@ If an empty string is given as the value, the property is reset to its default.` } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/status.go b/src/go/rpk/pkg/cli/cluster/config/status.go index 674fe54b44d07..f541ae950a675 100644 --- a/src/go/rpk/pkg/cli/cluster/config/status.go +++ b/src/go/rpk/pkg/cli/cluster/config/status.go @@ -33,8 +33,8 @@ a lower number shows that a node is out of sync, perhaps because it is offline.`, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/health.go b/src/go/rpk/pkg/cli/cluster/health.go index 28cb61a861670..6dcb88cd1bb21 100644 --- a/src/go/rpk/pkg/cli/cluster/health.go +++ b/src/go/rpk/pkg/cli/cluster/health.go @@ -40,8 +40,8 @@ following conditions are met: Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/license/info.go b/src/go/rpk/pkg/cli/cluster/license/info.go index 2744fa074dd11..886d10be53eb4 100644 --- a/src/go/rpk/pkg/cli/cluster/license/info.go +++ b/src/go/rpk/pkg/cli/cluster/license/info.go @@ -28,8 +28,8 @@ func newInfoCommand(fs afero.Fs, p *config.Params) *cobra.Command { `, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/license/set.go b/src/go/rpk/pkg/cli/cluster/license/set.go index 088bc0da5f190..4ee19216f8628 100644 --- a/src/go/rpk/pkg/cli/cluster/license/set.go +++ b/src/go/rpk/pkg/cli/cluster/license/set.go @@ -42,8 +42,8 @@ default location '/etc/redpanda/redpanda.license'. } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/logdirs.go b/src/go/rpk/pkg/cli/cluster/logdirs.go index 04c8de3c7eced..a09a7b915a266 100644 --- a/src/go/rpk/pkg/cli/cluster/logdirs.go +++ b/src/go/rpk/pkg/cli/cluster/logdirs.go @@ -71,7 +71,7 @@ where revision is a Redpanda internal concept. Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/maintenance/disable.go b/src/go/rpk/pkg/cli/cluster/maintenance/disable.go index 109f74735a036..88ada246b59f4 100644 --- a/src/go/rpk/pkg/cli/cluster/maintenance/disable.go +++ b/src/go/rpk/pkg/cli/cluster/maintenance/disable.go @@ -38,8 +38,8 @@ func newDisableCommand(fs afero.Fs, p *config.Params) *cobra.Command { } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/maintenance/enable.go b/src/go/rpk/pkg/cli/cluster/maintenance/enable.go index f293049a04c0a..8c9d311ca2cb2 100644 --- a/src/go/rpk/pkg/cli/cluster/maintenance/enable.go +++ b/src/go/rpk/pkg/cli/cluster/maintenance/enable.go @@ -44,8 +44,8 @@ node exists that is already in maintenance mode then an error will be returned. } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/maintenance/status.go b/src/go/rpk/pkg/cli/cluster/maintenance/status.go index bcd387b198e88..fa69cd6273180 100644 --- a/src/go/rpk/pkg/cli/cluster/maintenance/status.go +++ b/src/go/rpk/pkg/cli/cluster/maintenance/status.go @@ -82,8 +82,8 @@ Notes: Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/metadata.go b/src/go/rpk/pkg/cli/cluster/metadata.go index fc579517b970d..7d024946f2b53 100644 --- a/src/go/rpk/pkg/cli/cluster/metadata.go +++ b/src/go/rpk/pkg/cli/cluster/metadata.go @@ -55,7 +55,7 @@ In the broker section, the controller node is suffixed with *. `, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/partitions/cancel.go b/src/go/rpk/pkg/cli/cluster/partitions/cancel.go index a95bfd5a2229c..85b3d013c8ead 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/cancel.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/cancel.go @@ -59,8 +59,8 @@ occurring in the specified node: func (m *movementCancelHandler) runMovementCancel(cmd *cobra.Command, _ []string) { p, err := m.p.LoadVirtualProfile(m.fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(m.fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/partitions/list.go b/src/go/rpk/pkg/cli/cluster/partitions/list.go index daa20007e7ce8..4e0661d19129b 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/list.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/list.go @@ -91,8 +91,8 @@ List all in json format. out.Die("flag '--all' cannot be used with topic filters.\n%v", cmd.UsageString()) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/partitions/move.go b/src/go/rpk/pkg/cli/cluster/partitions/move.go index af9cd7fe4cb2e..2a0573540f83d 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/move.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/move.go @@ -53,8 +53,8 @@ func newMovePartitionReplicasCommand(fs afero.Fs, p *config.Params) *cobra.Comma } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/partitions/move_status.go b/src/go/rpk/pkg/cli/cluster/partitions/move_status.go index dd2466f42575b..734f6e211088a 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/move_status.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/move_status.go @@ -38,8 +38,8 @@ func newPartitionMovementsStatusCommand(fs afero.Fs, p *config.Params) *cobra.Co Long: helpListMovement, Run: func(cmd *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) // If partition(s) is specified but no topic(s) is specified, exit. if len(topics) <= 0 && len(partitions) > 0 { diff --git a/src/go/rpk/pkg/cli/cluster/partitions/status.go b/src/go/rpk/pkg/cli/cluster/partitions/status.go index d28d9fff2c639..54584d0643fde 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/status.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/status.go @@ -78,8 +78,8 @@ investigation. A few areas to investigate: Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/partitions/toggle.go b/src/go/rpk/pkg/cli/cluster/partitions/toggle.go index 69f6cbe85345e..9e3fcaba97b89 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/toggle.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/toggle.go @@ -65,8 +65,8 @@ Enable partition 1, and 2 of topic 'foo', and partition 5 of topic 'bar' in the os.Exit(1) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) @@ -140,8 +140,8 @@ Disable partition 1, and 2 of topic 'foo', and partition 5 of topic 'bar' in the os.Exit(1) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/partitions/unsafe_recover.go b/src/go/rpk/pkg/cli/cluster/partitions/unsafe_recover.go index ca4166a26fde7..d7e991e9f8a39 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/unsafe_recover.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/unsafe_recover.go @@ -47,8 +47,8 @@ using the '--dry' flag. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/selftest/start.go b/src/go/rpk/pkg/cli/cluster/selftest/start.go index 2821b31e27a51..009cb7bbac62e 100644 --- a/src/go/rpk/pkg/cli/cluster/selftest/start.go +++ b/src/go/rpk/pkg/cli/cluster/selftest/start.go @@ -55,8 +55,8 @@ command.`, Run: func(cmd *cobra.Command, _ []string) { // Load config settings p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) // Warn user before continuing, proceed only via explicit signal if !noConfirm { diff --git a/src/go/rpk/pkg/cli/cluster/selftest/status.go b/src/go/rpk/pkg/cli/cluster/selftest/status.go index 08032cddc2b91..3142152cb69ef 100644 --- a/src/go/rpk/pkg/cli/cluster/selftest/status.go +++ b/src/go/rpk/pkg/cli/cluster/selftest/status.go @@ -50,8 +50,8 @@ the jobs launched. Possible results are: Run: func(cmd *cobra.Command, _ []string) { // Load config settings p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) // Create new HTTP client for communication w/ admin server cl, err := adminapi.NewClient(fs, p) diff --git a/src/go/rpk/pkg/cli/cluster/selftest/stop.go b/src/go/rpk/pkg/cli/cluster/selftest/stop.go index 6dd27141d4b2d..c84689fc9a712 100644 --- a/src/go/rpk/pkg/cli/cluster/selftest/stop.go +++ b/src/go/rpk/pkg/cli/cluster/selftest/stop.go @@ -33,8 +33,8 @@ success when all jobs have been stopped or reports errors if broker timeouts hav Run: func(cmd *cobra.Command, _ []string) { // Load config settings p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) // Create new HTTP client for communication w/ admin server cl, err := adminapi.NewClient(fs, p) diff --git a/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go b/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go index 0aff8fcd64169..c8e191dcada84 100644 --- a/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go +++ b/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go @@ -37,8 +37,8 @@ If the wait flag (--wait/-w) is set, the command will poll the status of the recovery process until it's finished.`, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go b/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go index 0c74630f21f9a..7e34601aed782 100644 --- a/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go +++ b/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go @@ -29,8 +29,8 @@ This command fetches the status of the process of restoring topics from the archival bucket.`, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) client, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/txn/describe.go b/src/go/rpk/pkg/cli/cluster/txn/describe.go index 98871dfc0b514..5398e2ac0319c 100644 --- a/src/go/rpk/pkg/cli/cluster/txn/describe.go +++ b/src/go/rpk/pkg/cli/cluster/txn/describe.go @@ -64,7 +64,7 @@ If no transactional IDs are requested, all transactional IDs are printed. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go b/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go index 5cd5a46ff83dd..bab001de628a8 100644 --- a/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go +++ b/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go @@ -71,7 +71,7 @@ partitions with --partitions. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/txn/list.go b/src/go/rpk/pkg/cli/cluster/txn/list.go index 434ac42f83a9e..6ae78c1cd4acc 100644 --- a/src/go/rpk/pkg/cli/cluster/txn/list.go +++ b/src/go/rpk/pkg/cli/cluster/txn/list.go @@ -43,7 +43,7 @@ on what the columns in the output mean, see 'rpk cluster txn --help'. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/container/purge.go b/src/go/rpk/pkg/cli/container/purge.go index bf67f625e0008..06738e043039f 100644 --- a/src/go/rpk/pkg/cli/container/purge.go +++ b/src/go/rpk/pkg/cli/container/purge.go @@ -41,7 +41,7 @@ func newPurgeCommand(fs afero.Fs, p *config.Params) *cobra.Command { return } cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok || y.Profile(common.ContainerProfileName) == nil { diff --git a/src/go/rpk/pkg/cli/container/start.go b/src/go/rpk/pkg/cli/container/start.go index 2655a976b1d90..7e289a3180772 100644 --- a/src/go/rpk/pkg/cli/container/start.go +++ b/src/go/rpk/pkg/cli/container/start.go @@ -88,10 +88,10 @@ func newStartCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.MaybeDie(err, "unable to render cluster info: %v; you may run 'rpk container status' to retrieve the cluster info", err) cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, err := cfg.ActualRpkYamlOrEmpty() - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) err = common.CreateProfile(fs, c, y) if err == nil { diff --git a/src/go/rpk/pkg/cli/container/status.go b/src/go/rpk/pkg/cli/container/status.go index f68ee4ee28cbd..48d714d718f51 100644 --- a/src/go/rpk/pkg/cli/container/status.go +++ b/src/go/rpk/pkg/cli/container/status.go @@ -30,7 +30,7 @@ func newStatusCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.MaybeDieErr(common.WrapIfConnErr(err)) cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() var withProfile bool diff --git a/src/go/rpk/pkg/cli/debug/bundle/bundle.go b/src/go/rpk/pkg/cli/debug/bundle/bundle.go index 4e08db0d910d6..34310624ff8c0 100644 --- a/src/go/rpk/pkg/cli/debug/bundle/bundle.go +++ b/src/go/rpk/pkg/cli/debug/bundle/bundle.go @@ -87,9 +87,9 @@ func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.MaybeDie(err, "unable to determine filepath %q: %v", outFile, err) cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) - // We do not out.CheckExitCloudAdmin here, because + // We do not config.CheckExitCloudAdmin here, because // capturing a debug *can* have access sometimes (k8s). var ( p = cfg.VirtualProfile() diff --git a/src/go/rpk/pkg/cli/generate/app.go b/src/go/rpk/pkg/cli/generate/app.go index 89b4a27e4cbd5..a28dec067480a 100644 --- a/src/go/rpk/pkg/cli/generate/app.go +++ b/src/go/rpk/pkg/cli/generate/app.go @@ -88,7 +88,7 @@ func newAppCmd(fs afero.Fs, p *config.Params) *cobra.Command { Long: appHelpText, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) isInteractive := langFlag == "" // The rest of the flags are optional. if !isInteractive { diff --git a/src/go/rpk/pkg/cli/generate/prometheus.go b/src/go/rpk/pkg/cli/generate/prometheus.go index f5b5cb63d4f53..c9a3ca4b9a364 100644 --- a/src/go/rpk/pkg/cli/generate/prometheus.go +++ b/src/go/rpk/pkg/cli/generate/prometheus.go @@ -75,7 +75,7 @@ func newPrometheusConfigCmd(fs afero.Fs, p *config.Params) *cobra.Command { Long: prometheusHelpText, Run: func(cmd *cobra.Command, args []string) { y, err := p.LoadVirtualRedpandaYaml(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) yml, err := executePrometheusConfig(y, opts, tlsCfg, fs) out.MaybeDieErr(err) diff --git a/src/go/rpk/pkg/cli/group/describe.go b/src/go/rpk/pkg/cli/group/describe.go index 41bb1226d7c26..cd534fc515f56 100644 --- a/src/go/rpk/pkg/cli/group/describe.go +++ b/src/go/rpk/pkg/cli/group/describe.go @@ -35,7 +35,7 @@ information about the members. Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, groups []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/group/group.go b/src/go/rpk/pkg/cli/group/group.go index 9b6ca27ef67eb..1d04f88a59956 100644 --- a/src/go/rpk/pkg/cli/group/group.go +++ b/src/go/rpk/pkg/cli/group/group.go @@ -95,7 +95,7 @@ groups, or to list groups that need to be cleaned up. Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) @@ -142,7 +142,7 @@ quick investigation or testing. This command helps you do that. Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/group/offset_delete.go b/src/go/rpk/pkg/cli/group/offset_delete.go index 17b40fbe59e38..8348487a8dc9e 100644 --- a/src/go/rpk/pkg/cli/group/offset_delete.go +++ b/src/go/rpk/pkg/cli/group/offset_delete.go @@ -53,7 +53,7 @@ topic_b 0 Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/group/seek.go b/src/go/rpk/pkg/cli/group/seek.go index 4bb0d79b23420..71873d64b2bf8 100644 --- a/src/go/rpk/pkg/cli/group/seek.go +++ b/src/go/rpk/pkg/cli/group/seek.go @@ -87,7 +87,7 @@ Seek group G to the beginning of a topic it was not previously consuming: Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/iotune/iotune.go b/src/go/rpk/pkg/cli/iotune/iotune.go index 87b27df37e067..5534566ab4a5b 100644 --- a/src/go/rpk/pkg/cli/iotune/iotune.go +++ b/src/go/rpk/pkg/cli/iotune/iotune.go @@ -37,7 +37,7 @@ func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { timeout += duration y, err := p.LoadVirtualRedpandaYaml(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) var evalDirectories []string if len(directories) != 0 { diff --git a/src/go/rpk/pkg/cli/profile/clear.go b/src/go/rpk/pkg/cli/profile/clear.go index 1632b134a1321..0fb376600bdc4 100644 --- a/src/go/rpk/pkg/cli/profile/clear.go +++ b/src/go/rpk/pkg/cli/profile/clear.go @@ -28,7 +28,7 @@ prod cluster profile. Args: cobra.ExactArgs(0), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { return diff --git a/src/go/rpk/pkg/cli/profile/create.go b/src/go/rpk/pkg/cli/profile/create.go index 7b81b0e65880d..afa852f373ea2 100644 --- a/src/go/rpk/pkg/cli/profile/create.go +++ b/src/go/rpk/pkg/cli/profile/create.go @@ -89,7 +89,7 @@ rpk always switches to the newly created profile. Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, err := cfg.ActualRpkYamlOrEmpty() out.MaybeDie(err, "unable to load rpk.yaml: %v", err) @@ -163,11 +163,11 @@ func createProfile( return "", "", fmt.Errorf("unable to create docker client: %v", err) } err = container.CreateProfile(fs, c, y) //nolint:contextcheck // No need to pass the context, the underlying functions use a context with timeout. - if err != nil { return "", "", fmt.Errorf("unable to create profile from rpk container: %v", err) } return container.ContainerProfileName, "", nil + case fromCloud != "": var err error o, err = createCloudProfile(ctx, y, cfg, fromCloud) @@ -257,9 +257,9 @@ func createProfile( } func createCloudProfile(ctx context.Context, y *config.RpkYaml, cfg *config.Config, clusterID string) (CloudClusterOutputs, error) { - a := y.Auth(y.CurrentCloudAuth) + a := y.CurrentAuth() if a == nil { - return CloudClusterOutputs{}, fmt.Errorf("missing auth for current_cloud_auth %q", y.CurrentCloudAuth) + return CloudClusterOutputs{}, errors.New("missing current coud auth, please login with 'rpk cloud login'") } overrides := cfg.DevOverrides() @@ -269,7 +269,7 @@ func createCloudProfile(ctx context.Context, y *config.RpkYaml, cfg *config.Conf return CloudClusterOutputs{}, err } if expired { - return CloudClusterOutputs{}, fmt.Errorf("token for %q has expired, please login again", y.CurrentCloudAuth) + return CloudClusterOutputs{}, errors.New("current cloud auth has expired, please re-login with 'rpk cloud login'") } cl := cloudapi.NewClient(overrides.CloudAPIURL, a.AuthToken, httpapi.ReqTimeout(10*time.Second)) diff --git a/src/go/rpk/pkg/cli/profile/current.go b/src/go/rpk/pkg/cli/profile/current.go index 1507354cffe61..4602f8bdc8f55 100644 --- a/src/go/rpk/pkg/cli/profile/current.go +++ b/src/go/rpk/pkg/cli/profile/current.go @@ -31,7 +31,7 @@ be useful in scripts, or a PS1, or to confirm what you have selected. Args: cobra.ExactArgs(0), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) if !noNewline { defer fmt.Println() diff --git a/src/go/rpk/pkg/cli/profile/delete.go b/src/go/rpk/pkg/cli/profile/delete.go index dcf37ba099677..cd8b5c25bad44 100644 --- a/src/go/rpk/pkg/cli/profile/delete.go +++ b/src/go/rpk/pkg/cli/profile/delete.go @@ -32,7 +32,7 @@ is selected. ValidArgsFunction: ValidProfiles(fs, p), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { diff --git a/src/go/rpk/pkg/cli/profile/edit.go b/src/go/rpk/pkg/cli/profile/edit.go index e21b132bfc931..c9cc6c6e3b666 100644 --- a/src/go/rpk/pkg/cli/profile/edit.go +++ b/src/go/rpk/pkg/cli/profile/edit.go @@ -33,9 +33,9 @@ exist, this command creates it and switches to it. ValidArgsFunction: ValidProfiles(fs, p), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, err := cfg.ActualRpkYamlOrEmpty() - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) if len(args) == 0 { args = append(args, y.CurrentProfile) diff --git a/src/go/rpk/pkg/cli/profile/edit_gobals.go b/src/go/rpk/pkg/cli/profile/edit_gobals.go index 575e76291ecb3..942d04e8bed0c 100644 --- a/src/go/rpk/pkg/cli/profile/edit_gobals.go +++ b/src/go/rpk/pkg/cli/profile/edit_gobals.go @@ -30,9 +30,9 @@ This command opens your default editor to edit the rpk global configurations. Args: cobra.ExactArgs(0), Run: func(*cobra.Command, []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, err := cfg.ActualRpkYamlOrEmpty() - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y.Globals, err = rpkos.EditTmpYAMLFile(fs, y.Globals) out.MaybeDieErr(err) diff --git a/src/go/rpk/pkg/cli/profile/list.go b/src/go/rpk/pkg/cli/profile/list.go index f8672311eb9c0..d4dd890bec65b 100644 --- a/src/go/rpk/pkg/cli/profile/list.go +++ b/src/go/rpk/pkg/cli/profile/list.go @@ -26,7 +26,7 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(0), Run: func(*cobra.Command, []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) tw := out.NewTable("name", "description") defer tw.Flush() diff --git a/src/go/rpk/pkg/cli/profile/print.go b/src/go/rpk/pkg/cli/profile/print.go index e067a72c903e8..8cafe8893aa4c 100644 --- a/src/go/rpk/pkg/cli/profile/print.go +++ b/src/go/rpk/pkg/cli/profile/print.go @@ -32,7 +32,7 @@ in the rpk.yaml file. ValidArgsFunction: ValidProfiles(fs, p), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { diff --git a/src/go/rpk/pkg/cli/profile/print_globals.go b/src/go/rpk/pkg/cli/profile/print_globals.go index 32b4f9d220f26..20ccaabc32b82 100644 --- a/src/go/rpk/pkg/cli/profile/print_globals.go +++ b/src/go/rpk/pkg/cli/profile/print_globals.go @@ -26,7 +26,7 @@ func newPrintGlobalsCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(0), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y := cfg.VirtualRpkYaml() m, err := yaml.Marshal(y.Globals) diff --git a/src/go/rpk/pkg/cli/profile/prompt.go b/src/go/rpk/pkg/cli/profile/prompt.go index c139bfc24f0cd..87a46f6069318 100644 --- a/src/go/rpk/pkg/cli/profile/prompt.go +++ b/src/go/rpk/pkg/cli/profile/prompt.go @@ -71,7 +71,7 @@ Four modifiers are supported, "bold", "faint", "underline", and "invert". Args: cobra.ExactArgs(0), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) var errmsg string if validate { diff --git a/src/go/rpk/pkg/cli/profile/rename.go b/src/go/rpk/pkg/cli/profile/rename.go index 92a376a6fa8dc..a22b45b47e423 100644 --- a/src/go/rpk/pkg/cli/profile/rename.go +++ b/src/go/rpk/pkg/cli/profile/rename.go @@ -26,7 +26,7 @@ func newRenameToCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(1), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { diff --git a/src/go/rpk/pkg/cli/profile/set.go b/src/go/rpk/pkg/cli/profile/set.go index f1a46cadf5d08..599ec30541e29 100644 --- a/src/go/rpk/pkg/cli/profile/set.go +++ b/src/go/rpk/pkg/cli/profile/set.go @@ -45,7 +45,7 @@ You can also use the format 'set key value' if you intend to only set one key. ValidArgsFunction: validSetArgs, Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) // Other set commands are `set key value`, if people // use that older form by force of habit, we support diff --git a/src/go/rpk/pkg/cli/profile/set_globals.go b/src/go/rpk/pkg/cli/profile/set_globals.go index a6a1a45fe97c2..5a64a9c54c95b 100644 --- a/src/go/rpk/pkg/cli/profile/set_globals.go +++ b/src/go/rpk/pkg/cli/profile/set_globals.go @@ -39,7 +39,7 @@ format 'set key value' if you intend to only set one key. ValidArgsFunction: validSetGlobalArgs, Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) // Other set commands are `set key value`, if people // use that older form by force of habit, we support diff --git a/src/go/rpk/pkg/cli/profile/use.go b/src/go/rpk/pkg/cli/profile/use.go index 835e950753a9f..ca7e30d093156 100644 --- a/src/go/rpk/pkg/cli/profile/use.go +++ b/src/go/rpk/pkg/cli/profile/use.go @@ -26,7 +26,7 @@ func newUseCommand(fs afero.Fs, p *config.Params) *cobra.Command { ValidArgsFunction: ValidProfiles(fs, p), Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() if !ok { diff --git a/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go b/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go index 8a1b3762e1d20..0d62d849e3dc0 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go @@ -66,8 +66,8 @@ kafka/test/0 Run: func(cmd *cobra.Command, args []string) { broker, _ := strconv.Atoi(args[0]) p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission.go b/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission.go index 79fa58f1f817d..afe17dc88eecb 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission.go @@ -39,8 +39,8 @@ cluster is unreachable), use the hidden --force flag. } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/admin/brokers/list.go b/src/go/rpk/pkg/cli/redpanda/admin/brokers/list.go index 61ffd34ac47b9..cd09ad22444ba 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/brokers/list.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/brokers/list.go @@ -16,8 +16,8 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/admin/brokers/recommission.go b/src/go/rpk/pkg/cli/redpanda/admin/brokers/recommission.go index 04bfdaae717f2..4ec62277d0209 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/brokers/recommission.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/brokers/recommission.go @@ -35,8 +35,8 @@ the cluster leader handles the request. } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/admin/config/config.go b/src/go/rpk/pkg/cli/redpanda/admin/config/config.go index 0575155d5ce13..d802a52441eb3 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/config/config.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/config/config.go @@ -39,8 +39,8 @@ func newPrintCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := adminapi.NewHostClient(fs, p, host) out.MaybeDie(err, "unable to initialize admin client: %v", err) @@ -104,7 +104,7 @@ failure of enabling each logger is individually printed. Run: func(cmd *cobra.Command, loggers []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := adminapi.NewHostClient(fs, p, host) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go b/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go index 078ce2e72c885..30964f64a72cb 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go @@ -51,7 +51,7 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/check.go b/src/go/rpk/pkg/cli/redpanda/check.go index 09628fcf88669..28a190871d1b1 100644 --- a/src/go/rpk/pkg/cli/redpanda/check.go +++ b/src/go/rpk/pkg/cli/redpanda/check.go @@ -31,7 +31,7 @@ func NewCheckCommand(fs afero.Fs, p *config.Params) *cobra.Command { Short: "Check if system meets redpanda requirements", Run: func(_ *cobra.Command, args []string) { y, err := p.LoadVirtualRedpandaYaml(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) err = executeCheck(fs, y, timeout) out.MaybeDie(err, "unable to check: %v", err) }, diff --git a/src/go/rpk/pkg/cli/redpanda/config.go b/src/go/rpk/pkg/cli/redpanda/config.go index 959e3aec4e195..4c4e3dca91045 100644 --- a/src/go/rpk/pkg/cli/redpanda/config.go +++ b/src/go/rpk/pkg/cli/redpanda/config.go @@ -76,7 +76,7 @@ You may also use = notation for setting configuration properties: Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) var key, value string if len(args) == 1 && strings.Contains(args[0], "=") { @@ -133,7 +133,7 @@ you must use the --self flag to specify which ip redpanda should listen on. Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { cfg, err := p.Load(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) y := cfg.ActualRedpandaYamlOrDefaults() // we modify fields in the raw file without writing env / flag overrides seeds, err := parseSeedIPs(ips) diff --git a/src/go/rpk/pkg/cli/redpanda/mode.go b/src/go/rpk/pkg/cli/redpanda/mode.go index 4ad835208bb05..6a741dd4e369f 100644 --- a/src/go/rpk/pkg/cli/redpanda/mode.go +++ b/src/go/rpk/pkg/cli/redpanda/mode.go @@ -59,7 +59,7 @@ func NewModeCommand(fs afero.Fs, p *config.Params) *cobra.Command { func executeMode(fs afero.Fs, p *config.Params, mode string) error { cfg, err := p.Load(fs) if err != nil { - return fmt.Errorf("unable to load config: %v", err) + return fmt.Errorf("rpk unable to load config: %v", err) } return cfg.SetMode(fs, mode) } diff --git a/src/go/rpk/pkg/cli/redpanda/stop.go b/src/go/rpk/pkg/cli/redpanda/stop.go index 7b365d297d538..efdd428ef2358 100644 --- a/src/go/rpk/pkg/cli/redpanda/stop.go +++ b/src/go/rpk/pkg/cli/redpanda/stop.go @@ -38,7 +38,7 @@ hasn't stopped, it sends SIGTERM. Lastly, it sends SIGKILL if it's still running.`, Run: func(_ *cobra.Command, args []string) { y, err := p.LoadVirtualRedpandaYaml(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) err = executeStop(fs, y, timeout) out.MaybeDieErr(err) diff --git a/src/go/rpk/pkg/cli/redpanda/tune/list.go b/src/go/rpk/pkg/cli/redpanda/tune/list.go index cb839cc65dfbc..9e7a444640620 100644 --- a/src/go/rpk/pkg/cli/redpanda/tune/list.go +++ b/src/go/rpk/pkg/cli/redpanda/tune/list.go @@ -50,7 +50,7 @@ You may use 'rpk redpanda config set' to enable or disable a tuner. Args: cobra.ExactArgs(0), Run: func(*cobra.Command, []string) { y, err := p.LoadVirtualRedpandaYaml(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) // Using cpu mask and timeout defaults since we are not executing // any tuner. diff --git a/src/go/rpk/pkg/cli/redpanda/tune/tune.go b/src/go/rpk/pkg/cli/redpanda/tune/tune.go index 0781a7d17e1be..bc9ad7c095d62 100644 --- a/src/go/rpk/pkg/cli/redpanda/tune/tune.go +++ b/src/go/rpk/pkg/cli/redpanda/tune/tune.go @@ -90,7 +90,7 @@ To learn more about a tuner, run 'rpk redpanda tune help '. tunerParams.CPUMask = cpuMask y, err := p.LoadVirtualRedpandaYaml(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) var tunerFactory factory.TunersFactory if outTuneScriptFile != "" { exists, err := afero.Exists(fs, outTuneScriptFile) diff --git a/src/go/rpk/pkg/cli/registry/compatibility_level.go b/src/go/rpk/pkg/cli/registry/compatibility_level.go index 5f30d39f91837..25d22c3e87921 100644 --- a/src/go/rpk/pkg/cli/registry/compatibility_level.go +++ b/src/go/rpk/pkg/cli/registry/compatibility_level.go @@ -58,7 +58,7 @@ per-subject levels. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) @@ -90,7 +90,7 @@ func compatSetCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go b/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go index 4acf3d761d539..e7d51d55ffe72 100644 --- a/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go +++ b/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go @@ -43,7 +43,7 @@ func newCheckCompatibilityCommand(fs afero.Fs, p *config.Params) *cobra.Command out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/registry/schema/create.go b/src/go/rpk/pkg/cli/registry/schema/create.go index 6bc5d895a2ae8..fd6c861231e5f 100644 --- a/src/go/rpk/pkg/cli/registry/schema/create.go +++ b/src/go/rpk/pkg/cli/registry/schema/create.go @@ -62,7 +62,7 @@ version 1: out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/registry/schema/delete.go b/src/go/rpk/pkg/cli/registry/schema/delete.go index 5b30b003a6e12..c4f52545def4d 100644 --- a/src/go/rpk/pkg/cli/registry/schema/delete.go +++ b/src/go/rpk/pkg/cli/registry/schema/delete.go @@ -38,7 +38,7 @@ func newDeleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/registry/schema/get.go b/src/go/rpk/pkg/cli/registry/schema/get.go index 7197f770a80b5..a2ee53d4b2a50 100644 --- a/src/go/rpk/pkg/cli/registry/schema/get.go +++ b/src/go/rpk/pkg/cli/registry/schema/get.go @@ -59,7 +59,7 @@ To print the schema, use the '--print-schema' flag. out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/registry/schema/list.go b/src/go/rpk/pkg/cli/registry/schema/list.go index 51bdbe3c45c9e..ccaf5c037fd1f 100644 --- a/src/go/rpk/pkg/cli/registry/schema/list.go +++ b/src/go/rpk/pkg/cli/registry/schema/list.go @@ -41,7 +41,7 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/registry/schema/references.go b/src/go/rpk/pkg/cli/registry/schema/references.go index 3d5337cd081cc..0788bf1bf06e4 100644 --- a/src/go/rpk/pkg/cli/registry/schema/references.go +++ b/src/go/rpk/pkg/cli/registry/schema/references.go @@ -31,7 +31,7 @@ func newReferencesCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/registry/subject.go b/src/go/rpk/pkg/cli/registry/subject.go index fcba275c62f82..76d1640dfbc75 100644 --- a/src/go/rpk/pkg/cli/registry/subject.go +++ b/src/go/rpk/pkg/cli/registry/subject.go @@ -50,7 +50,7 @@ func subjectListCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) @@ -95,7 +95,7 @@ func subjectDeleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit(h) } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := schemaregistry.NewClient(fs, p) out.MaybeDie(err, "unable to initialize schema registry client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/add_partitions.go b/src/go/rpk/pkg/cli/topic/add_partitions.go index a7d8664459dac..e6bede57089fd 100644 --- a/src/go/rpk/pkg/cli/topic/add_partitions.go +++ b/src/go/rpk/pkg/cli/topic/add_partitions.go @@ -41,7 +41,7 @@ func newAddPartitionsCommand(fs afero.Fs, p *config.Params) *cobra.Command { } } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/config.go b/src/go/rpk/pkg/cli/topic/config.go index 307864843409d..e174559485a44 100644 --- a/src/go/rpk/pkg/cli/topic/config.go +++ b/src/go/rpk/pkg/cli/topic/config.go @@ -53,7 +53,7 @@ valid, but does not apply it. Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := kafka.NewFranzClient(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/consume.go b/src/go/rpk/pkg/cli/topic/consume.go index 7e17e6f299e33..a5fd56e25f3c9 100644 --- a/src/go/rpk/pkg/cli/topic/consume.go +++ b/src/go/rpk/pkg/cli/topic/consume.go @@ -83,7 +83,7 @@ func newConsumeCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize admin kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/create.go b/src/go/rpk/pkg/cli/topic/create.go index dbf1cb0481095..eb36fe28ea22d 100644 --- a/src/go/rpk/pkg/cli/topic/create.go +++ b/src/go/rpk/pkg/cli/topic/create.go @@ -52,7 +52,7 @@ the cleanup.policy=compact config option set. Run: func(cmd *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := kafka.NewFranzClient(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/delete.go b/src/go/rpk/pkg/cli/topic/delete.go index 845399dbb11e8..1d8406b22c236 100644 --- a/src/go/rpk/pkg/cli/topic/delete.go +++ b/src/go/rpk/pkg/cli/topic/delete.go @@ -53,7 +53,7 @@ For example, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/describe.go b/src/go/rpk/pkg/cli/topic/describe.go index e0329fc96f79c..06703e7fd0517 100644 --- a/src/go/rpk/pkg/cli/topic/describe.go +++ b/src/go/rpk/pkg/cli/topic/describe.go @@ -47,7 +47,7 @@ partitions section. By default, the summary and configs sections are printed. Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, topicArg []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := kafka.NewFranzClient(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/describe_storage.go b/src/go/rpk/pkg/cli/topic/describe_storage.go index fa606d037e5d6..69d4e026c9e82 100644 --- a/src/go/rpk/pkg/cli/topic/describe_storage.go +++ b/src/go/rpk/pkg/cli/topic/describe_storage.go @@ -54,8 +54,8 @@ func newDescribeStorageCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) - out.CheckExitCloudAdmin(p) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) cl, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/list.go b/src/go/rpk/pkg/cli/topic/list.go index 44ce840fffa25..741ada4411ed4 100644 --- a/src/go/rpk/pkg/cli/topic/list.go +++ b/src/go/rpk/pkg/cli/topic/list.go @@ -60,7 +60,7 @@ information. } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/produce.go b/src/go/rpk/pkg/cli/topic/produce.go index 5c7158877d22e..7d6bb2dc8640e 100644 --- a/src/go/rpk/pkg/cli/topic/produce.go +++ b/src/go/rpk/pkg/cli/topic/produce.go @@ -154,7 +154,7 @@ func newProduceCommand(fs afero.Fs, p *config.Params) *cobra.Command { // We are now ready to produce. p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) cl, err := kafka.NewFranzClient(fs, p, opts...) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/trim.go b/src/go/rpk/pkg/cli/topic/trim.go index a49d52e2bf528..6aefb913b1efa 100644 --- a/src/go/rpk/pkg/cli/topic/trim.go +++ b/src/go/rpk/pkg/cli/topic/trim.go @@ -71,7 +71,7 @@ Trim records from a JSON file `, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) adm, err := kafka.NewAdmin(fs, p) out.MaybeDie(err, "unable to initialize kafka client: %v", err) diff --git a/src/go/rpk/pkg/cli/transform/delete.go b/src/go/rpk/pkg/cli/transform/delete.go index 105d1edc6eac9..211215416c1ab 100644 --- a/src/go/rpk/pkg/cli/transform/delete.go +++ b/src/go/rpk/pkg/cli/transform/delete.go @@ -27,7 +27,7 @@ func newDeleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) api, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin api client: %v", err) diff --git a/src/go/rpk/pkg/cli/transform/deploy.go b/src/go/rpk/pkg/cli/transform/deploy.go index 5e8f4305cbc2c..158b96bb13bdf 100644 --- a/src/go/rpk/pkg/cli/transform/deploy.go +++ b/src/go/rpk/pkg/cli/transform/deploy.go @@ -61,7 +61,7 @@ The --var flag can be repeated to specify multiple variables like so: Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) api, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin api client: %v", err) diff --git a/src/go/rpk/pkg/cli/transform/list.go b/src/go/rpk/pkg/cli/transform/list.go index de5c581733516..e4fc5e85fc76b 100644 --- a/src/go/rpk/pkg/cli/transform/list.go +++ b/src/go/rpk/pkg/cli/transform/list.go @@ -49,7 +49,7 @@ The --detailed flag (-d) opts in to printing extra per-processor information. } p, err := p.LoadVirtualProfile(fs) - out.MaybeDie(err, "unable to load config: %v", err) + out.MaybeDie(err, "rpk unable to load config: %v", err) api, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin api client: %v", err) diff --git a/src/go/rpk/pkg/config/config.go b/src/go/rpk/pkg/config/config.go index 09de2725d286d..ad3865a821fba 100644 --- a/src/go/rpk/pkg/config/config.go +++ b/src/go/rpk/pkg/config/config.go @@ -13,6 +13,7 @@ import ( "fmt" "strings" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" "github.com/spf13/afero" ) @@ -78,6 +79,14 @@ type Config struct { devOverrides DevOverrides } +// CheckExitCloudAdmin exits if the profile has FromCloud=true and no +// ALLOW_RPK_CLOUD_ADMIN override. +func CheckExitCloudAdmin(p *RpkProfile) { + if p.FromCloud && !p.DevOverrides().AllowRpkCloudAdmin { + out.Die("This admin API based command is not supported on Redpanda Cloud clusters.") + } +} + // VirtualRedpandaYaml returns a redpanda.yaml, starting with defaults, // then decoding a potential file, then applying env vars and then flags. func (c *Config) VirtualRedpandaYaml() *RedpandaYaml { @@ -120,6 +129,12 @@ func (c *Config) VirtualProfile() *RpkProfile { return c.rpkYaml.Profile(c.rpkYaml.CurrentProfile) } +// ActualProfile returns an actual rpk.yaml's current profile. +// This may return nil if there is no current profile. +func (c *Config) ActualProfile() *RpkProfile { + return c.rpkYamlActual.Profile(c.rpkYamlActual.CurrentProfile) +} + // ActualRpkYaml returns an actual rpk.yaml if it exists, with no other // defaults over overrides applied. func (c *Config) ActualRpkYaml() (*RpkYaml, bool) { diff --git a/src/go/rpk/pkg/config/params.go b/src/go/rpk/pkg/config/params.go index 13f1a19e15ab1..d909158fb1b24 100644 --- a/src/go/rpk/pkg/config/params.go +++ b/src/go/rpk/pkg/config/params.go @@ -10,6 +10,7 @@ package config import ( + "bytes" "errors" "fmt" "net" @@ -28,11 +29,13 @@ import ( "golang.org/x/exp/maps" "golang.org/x/term" + "github.com/mattn/go-isatty" "github.com/spf13/afero" "github.com/spf13/cobra" "gopkg.in/yaml.v3" rpknet "github.com/redpanda-data/redpanda/src/go/rpk/pkg/net" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" ) const ( @@ -359,7 +362,13 @@ var xflags = map[string]xflag{ "anystring", xkindCloudAuth, func(v string, y *RpkYaml) error { - auth := y.Auth(y.CurrentCloudAuth) + p := y.Profile(y.CurrentProfile) + auth := p.VirtualAuth() + if auth == nil { + // If there is no actual current auth, we the virtual + // yaml has a "default" CloudCurrentAuth with no org. + auth = y.CurrentAuth() + } auth.ClientID = v return nil }, @@ -369,7 +378,11 @@ var xflags = map[string]xflag{ "anysecret", xkindCloudAuth, func(v string, y *RpkYaml) error { - auth := y.Auth(y.CurrentCloudAuth) + p := y.Profile(y.CurrentProfile) + auth := p.VirtualAuth() + if auth == nil { + auth = y.CurrentAuth() + } auth.ClientSecret = v return nil }, @@ -628,8 +641,8 @@ registry.hosts=localhost:8081,rp.example.com:8081 registry.tls.enabled=false A boolean that enables rpk to speak TLS to your broker's schema registry API - listeners. You can use this if you have well known certificates setup on your - schema registry API. If you use mTLS, specifying mTLS certificate filepaths + listeners. You can use this if you have well known certificates setup on your + schema registry API. If you use mTLS, specifying mTLS certificate filepaths automatically opts into TLS enabled. registry.tls.insecure_skip_verify=false @@ -637,8 +650,8 @@ registry.tls.insecure_skip_verify=false registry.tls.ca=/path/to/ca.pem A filepath to a PEM encoded CA certificate file to talk to your broker's - schema registry API listeners with mTLS. You may also need this if your - listeners are using a certificate by a well known authority that is not yet + schema registry API listeners with mTLS. You may also need this if your + listeners are using a certificate by a well known authority that is not yet bundled on your operating system. registry.tls.cert=/path/to/cert.pem @@ -651,9 +664,13 @@ registry.tls.key=/path/to/key.pem cloud.client_id=somestring An oauth client ID to use for authenticating with the Redpanda Cloud API. + Overrides the client ID in the current profile if it is for a cloud cluster, + otherwise overrides the default cloud auth client ID. cloud.client_secret=somelongerstring An oauth client secret to use for authenticating with the Redpanda Cloud API. + Overrides the client secret in the current profile if it is for a cloud + cluster, otherwise overrides the default cloud auth client secret. globals.prompt="%n" A format string to use for the default prompt; see 'rpk profile prompt' for @@ -917,9 +934,6 @@ func (p *Params) Load(fs afero.Fs) (*Config, error) { }() } - if err := p.backcompatOldCloudYaml(fs); err != nil { - return nil, err - } if err := p.readRpkConfig(fs, c); err != nil { return nil, err } @@ -927,6 +941,14 @@ func (p *Params) Load(fs afero.Fs) (*Config, error) { return nil, err } + if err := c.promptDeleteOldRpkYaml(fs); err != nil { // delete auths with no org/orgID, and profiles with no auth + return nil, err + } + if err := c.cleanupBadYaml(fs); err != nil { + return nil, err + } + c.ensureVirtualHasDefaults() // cleanupBadYaml deletes our defaults, this is an easier fix than trying to do it "right" + c.mergeRpkIntoRedpanda(true) // merge actual rpk.yaml KafkaAPI,AdminAPI,Tuners into redpanda.yaml rpk section c.addUnsetRedpandaDefaults(true) // merge from actual redpanda.yaml redpanda section to rpk section c.ensureRpkProfile() // ensure Virtual rpk.yaml has a loaded profile @@ -1037,86 +1059,6 @@ func readFile(fs afero.Fs, path string) (string, []byte, error) { return abs, file, err } -func (*Params) backcompatOldCloudYaml(fs afero.Fs) error { - def, err := DefaultRpkYamlPath() - if err != nil { - //nolint:nilerr // This error only happens if the user unset $HOME, and - // if they do that, we will avoid failing / avoid backcompat here. - return nil - } - - // Read and parse the old file. If it does not exist, that's great. - oldPath := filepath.Join(filepath.Dir(def), "__cloud.yaml") - _, raw, err := readFile(fs, oldPath) - if err != nil { - if errors.Is(err, afero.ErrFileNotFound) { - return nil - } - return fmt.Errorf("unable to backcompat __cloud.yaml file: %v", err) - } - var old struct { - ClientID string `yaml:"client_id"` - ClientSecret string `yaml:"client_secret"` - AuthToken string `yaml:"auth_token"` - } - if err := yaml.Unmarshal(raw, &old); err != nil { - return fmt.Errorf("unable to yaml decode %s: %v", oldPath, err) - } - - // For the rpk.yaml, if it does not exist, we will create it. - // We only support the default path, not any --config override. - // We do not want to migrate into some custom path. - _, rawRpkYaml, err := readFile(fs, def) - if err != nil && !errors.Is(err, afero.ErrFileNotFound) { - return fmt.Errorf("unable to read %s: %v", def, err) - } - var rpkYaml RpkYaml - if errors.Is(err, afero.ErrFileNotFound) { - rpkYaml = emptyVirtualRpkYaml() - } else { - if err := yaml.Unmarshal(rawRpkYaml, &rpkYaml); err != nil { - return fmt.Errorf("unable to yaml decode %s: %v", def, err) - } - if rpkYaml.Version < 1 { - return fmt.Errorf("%s is not in the expected rpk.yaml format", def) - } else if rpkYaml.Version < currentRpkYAMLVersion { - rpkYaml.Version = currentRpkYAMLVersion - } else if rpkYaml.Version > currentRpkYAMLVersion { - return fmt.Errorf("%s is using a newer rpk.yaml format than we understand, please upgrade rpk", def) - } - } - rpkYaml.fileLocation = def - - var exists bool - for _, a := range rpkYaml.CloudAuths { - if a.ClientID == old.ClientID && a.ClientSecret == old.ClientSecret { - exists = true - break - } - } - if !exists { - a := RpkCloudAuth{ - Name: "for_byoc", - Description: "Client ID and Secret for BYOC", - ClientID: old.ClientID, - ClientSecret: old.ClientSecret, - AuthToken: old.AuthToken, - } - rpkYaml.PushAuth(a) - if rpkYaml.CurrentCloudAuth == "" { - rpkYaml.CurrentCloudAuth = a.Name - } - if err := rpkYaml.Write(fs); err != nil { - return fmt.Errorf("unable to migrate %s to %s: %v", oldPath, def, err) - } - } - // If we fail at removing the old file, that's ok. We will try again - // the next time this command runs. - fs.Remove(oldPath) - - return nil -} - func (p *Params) readRpkConfig(fs afero.Fs, c *Config) error { def, err := DefaultRpkYamlPath() path := def @@ -1212,6 +1154,202 @@ func (p *Params) readRedpandaConfig(fs afero.Fs, c *Config) error { return nil } +func (c *Config) promptDeleteOldRpkYaml(fs afero.Fs) error { + if !c.rpkYamlExists { + return nil + } + var deleteAuthNames []string + var deleteProfileNames []string + for _, a := range c.rpkYamlActual.CloudAuths { + if a.Organization == "" || a.OrgID == "" { + deleteAuthNames = append(deleteAuthNames, a.Name) + continue + } + } + for _, p := range c.rpkYamlActual.Profiles { + cc := &p.CloudCluster + if p.FromCloud && c.rpkYamlActual.LookupAuth(cc.AuthOrgID, cc.AuthKind) == nil { + deleteProfileNames = append(deleteProfileNames, p.Name) + continue + } + } + + if len(deleteAuthNames) == 0 && len(deleteProfileNames) == 0 { + return nil + } + + if !isatty.IsTerminal(os.Stdin.Fd()) && !isatty.IsCygwinTerminal(os.Stdin.Fd()) { + return fmt.Errorf("rpk found invalid cloud auths or profiles, but is not running in a terminal, we cannot prompt if it is ok to delete the old auths or profiles") + } + if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) { + return fmt.Errorf("rpk found invalid cloud auths or profiles, but is not running in a terminal, we cannot prompt if it is ok to delete the old auths or profiles") + } + + if len(deleteAuthNames) > 0 { + fmt.Print(`rpk has found cloud authentications that are missing their organization or +organization ID fields. Either you previously logged into the cloud via an old +rpk, or the underlying rpk.yaml file was manually modified to remove these +fields. + +rpk needs to remove these auths, meaning you will need to log in again via +'rpk cloud login'. + +Affected auths: +`) + for _, name := range deleteAuthNames { + fmt.Printf(" %s\n", name) + } + fmt.Println() + } + if len(deleteProfileNames) > 0 { + fmt.Print(`rpk has found cloud profiles that do not have an attached authentication name. +Either you previously logged into the cloud via an old rpk, or the underlying +rpk.yaml file was manually modified to remove fields. + +rpk needs to remove these profiles, meaning you will need to create them again via +'rpk profile create --from-cluster'. + +Affected profiles: +`) + for _, name := range deleteProfileNames { + fmt.Printf(" %s\n", name) + } + fmt.Println() + } + confirm, err := out.Confirm("Confirm deletion of auths or profiles?") + if err != nil { + return fmt.Errorf("unable to confirm deletion: %w", err) + } + if !confirm { + return fmt.Errorf("deletion of old auths or profiles rejected, please use an older rpk") + } + + delAuths := make(map[string]struct{}) + for _, name := range deleteAuthNames { + delAuths[name] = struct{}{} + } + delProfiles := make(map[string]struct{}) + for _, name := range deleteProfileNames { + delProfiles[name] = struct{}{} + } + + for _, y := range []*RpkYaml{&c.rpkYaml, &c.rpkYamlActual} { + keepAuths := y.CloudAuths[:0] + for _, a := range y.CloudAuths { + if _, ok := delAuths[a.Name]; ok { + continue + } + keepAuths = append(keepAuths, a) + } + y.CloudAuths = keepAuths + keepProfiles := y.Profiles[:0] + for _, p := range y.Profiles { + if _, ok := delProfiles[p.Name]; ok { + continue + } + keepProfiles = append(keepProfiles, p) + } + y.Profiles = keepProfiles + } + if err := c.rpkYamlActual.Write(fs); err != nil { + return fmt.Errorf("unable to write rpk.yaml: %w", err) + } + return nil +} + +func (c *Config) cleanupBadYaml(fs afero.Fs) error { + if !c.rpkYamlExists { + return nil + } + + both := func(fn func(y *RpkYaml)) { + fn(&c.rpkYamlActual) + fn(&c.rpkYaml) + } + + // We + // * Delete duplicate auths + // * Delete duplicate profiles + // * Delete any auth that is kind == uninitialized + // * Delete any auth that is missing any of name,org,orgID + // * Clear the current profile if the referred profile does not exist + // * Clear the current auth if the referred auth does not exist + // * For all profiles, clear CloudCluster.Auth{OrgID,Kind} if the referred auth does not exist + jActBefore, _ := yaml.Marshal(c.rpkYamlActual) + both(func(y *RpkYaml) { + dupeFirstAuth := make(map[string]struct{}) + keepAuths := y.CloudAuths[:0] + for _, a := range y.CloudAuths { + if a.AnyKind() == CloudAuthUninitialized { + continue + } + if a.Name == "" || a.Organization == "" || a.OrgID == "" { + continue + } + if _, ok := dupeFirstAuth[a.Name]; ok { + continue + } + dupeFirstAuth[a.Name] = struct{}{} + keepAuths = append(keepAuths, a) + } + y.CloudAuths = keepAuths + + dupeFirstProfile := make(map[string]struct{}) + keepProfiles := y.Profiles[:0] + for _, p := range y.Profiles { + if _, ok := dupeFirstProfile[p.Name]; ok { + continue + } + if y.LookupAuth(p.CloudCluster.AuthOrgID, p.CloudCluster.AuthKind) == nil { + p.CloudCluster.AuthOrgID = "" + p.CloudCluster.AuthKind = "" + } + dupeFirstProfile[p.Name] = struct{}{} + keepProfiles = append(keepProfiles, p) + } + y.Profiles = keepProfiles + + if y.Profile(y.CurrentProfile) == nil { + y.CurrentProfile = "" + } + if y.CurrentAuth() == nil { + y.CurrentCloudAuthOrgID = "" + y.CurrentCloudAuthKind = "" + } + }) + jActAfter, _ := yaml.Marshal(c.rpkYamlActual) + if !bytes.Equal(jActBefore, jActAfter) { + if err := c.rpkYamlActual.Write(fs); err != nil { + return fmt.Errorf("unable to write rpk.yaml: %w", err) + } + } + return nil +} + +// We take a lazy approach with deleting bad yaml, which also deletes +// defaults from the virtual yaml. We add them back here. We could be +// smarter and avoiding deleting virtual defaults to begin with, but +// well, this is easy. +func (c *Config) ensureVirtualHasDefaults() { + y := &c.rpkYaml + def, _ := defaultVirtualRpkYaml() // must succeed, succeeded previously + if len(y.CloudAuths) == 0 { + y.CloudAuths = def.CloudAuths + } + if y.CurrentCloudAuthOrgID == "" { + y.CurrentCloudAuthOrgID = def.CurrentCloudAuthOrgID + } + if y.CurrentCloudAuthKind == "" { + y.CurrentCloudAuthKind = def.CurrentCloudAuthKind + } + if len(y.Profiles) == 0 { + y.Profiles = def.Profiles + } + if y.CurrentProfile == "" { + y.CurrentProfile = def.CurrentProfile + } +} + // We merge rpk.yaml files into our Virtual redpanda.yaml rpk section, // only if the rpk section contains relevant bits of information. // @@ -1255,20 +1393,24 @@ func (c *Config) ensureRpkProfile() { } // This function ensures a current auth exists in the Virtual rpk.yaml. +// This does not touch an auth that is pointed to by the current profile. +// Users of virtual auth are expected to first check the profile auth, then +// check the default auth. func (c *Config) ensureRpkCloudAuth() { dst := &c.rpkYaml - auth := dst.Auth(dst.CurrentCloudAuth) + auth := dst.CurrentAuth() if auth != nil { return } def := DefaultRpkCloudAuth() - dst.CurrentCloudAuth = def.Name - auth = dst.Auth(dst.CurrentCloudAuth) + dst.CurrentCloudAuthOrgID = def.OrgID + dst.CurrentCloudAuthKind = string(CloudAuthUninitialized) + auth = dst.CurrentAuth() if auth != nil { return } - dst.PushAuth(def) + dst.PushNewAuth(def) } func (c *Config) ensureBrokerAddrs() { diff --git a/src/go/rpk/pkg/config/params_test.go b/src/go/rpk/pkg/config/params_test.go index a6f91c977d2ca..921d6da6052d0 100644 --- a/src/go/rpk/pkg/config/params_test.go +++ b/src/go/rpk/pkg/config/params_test.go @@ -1077,8 +1077,10 @@ func TestXSetExamples(t *testing.T) { delete(m, x) xf := xflags[x] - y, _ := defaultVirtualRpkYaml() - if err := xf.parse(xf.testExample, &y); err != nil { + fs := afero.NewMemMapFs() + cfg, _ := new(Params).Load(fs) + y := cfg.VirtualRpkYaml() + if err := xf.parse(xf.testExample, y); err != nil { t.Errorf("unable to parse test example for xflag %s: %v", x, err) } yamlPath := yamlPaths[i] diff --git a/src/go/rpk/pkg/config/rpk_yaml.go b/src/go/rpk/pkg/config/rpk_yaml.go index 277da8fd634eb..bc05f908e86aa 100644 --- a/src/go/rpk/pkg/config/rpk_yaml.go +++ b/src/go/rpk/pkg/config/rpk_yaml.go @@ -43,7 +43,9 @@ func defaultVirtualRpkYaml() (RpkYaml, error) { CloudAuths: []RpkCloudAuth{DefaultRpkCloudAuth()}, } y.CurrentProfile = y.Profiles[0].Name - y.CurrentCloudAuth = y.CloudAuths[0].Name + y.CurrentCloudAuthOrgID = y.CloudAuths[0].OrgID + k, _ := y.CloudAuths[0].Kind() + y.CurrentCloudAuthKind = string(k) return y, nil } @@ -60,8 +62,9 @@ func DefaultRpkProfile() RpkProfile { // auth exists. func DefaultRpkCloudAuth() RpkCloudAuth { return RpkCloudAuth{ - Name: "default", - Description: "Default rpk cloud auth", + Name: "default", + Organization: "Default organization", + OrgID: "default-org-no-id", } } @@ -88,10 +91,11 @@ type ( Globals RpkGlobals `json:"globals,omitempty" yaml:"globals,omitempty"` - CurrentProfile string `json:"current_profile" yaml:"current_profile"` - CurrentCloudAuth string `json:"current_cloud_auth" yaml:"current_cloud_auth"` - Profiles []RpkProfile `json:"profiles,omitempty" yaml:"profiles,omitempty"` - CloudAuths []RpkCloudAuth `json:"cloud_auth,omitempty" yaml:"cloud_auth,omitempty"` + CurrentProfile string `json:"current_profile" yaml:"current_profile"` + CurrentCloudAuthOrgID string `json:"current_cloud_auth_org_id" yaml:"current_cloud_auth_org_id"` + CurrentCloudAuthKind string `json:"current_cloud_auth_kind" yaml:"current_cloud_auth_kind"` + Profiles []RpkProfile `json:"profiles,omitempty" yaml:"profiles,omitempty"` + CloudAuths []RpkCloudAuth `json:"cloud_auth,omitempty" yaml:"cloud_auth,omitempty"` } RpkGlobals struct { @@ -134,7 +138,7 @@ type ( Description string `json:"description,omitempty" yaml:"description,omitempty"` Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"` FromCloud bool `json:"from_cloud,omitempty" yaml:"from_cloud,omitempty"` - CloudCluster *RpkCloudCluster `json:"cloud_cluster,omitempty" yaml:"cloud_cluster,omitempty"` + CloudCluster RpkCloudCluster `json:"cloud_cluster,omitempty" yaml:"cloud_cluster,omitempty"` KafkaAPI RpkKafkaAPI `json:"kafka_api,omitempty" yaml:"kafka_api,omitempty"` AdminAPI RpkAdminAPI `json:"admin_api,omitempty" yaml:"admin_api,omitempty"` SR RpkSchemaRegistryAPI `json:"schema_registry,omitempty" yaml:"schema_registry,omitempty"` @@ -145,14 +149,19 @@ type ( } RpkCloudCluster struct { - Namespace string `json:"namespace" yaml:"namespace"` - Cluster string `json:"cluster" yaml:"cluster"` - Auth string `json:"auth" yaml:"auth"` + Namespace string `json:"namespace" yaml:"namespace"` + ClusterID string `json:"cluster_id" yaml:"cluster_id"` + ClusterName string `json:"cluster_name" yaml:"cluster_name"` + AuthOrgID string `json:"auth_org_id" yaml:"auth_org_id"` + AuthKind string `json:"auth_kind" yaml:"auth_kind"` } + // RpkCloudAuth is unique by name and org ID. We support multiple auths + // per org ID in case a person wants to use client credentials and SSO. RpkCloudAuth struct { Name string `json:"name" yaml:"name"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` + Organization string `json:"organization,omitempty" yaml:"organization,omitempty"` + OrgID string `json:"org_id,omitempty" yaml:"org_id,omitempty"` AuthToken string `json:"auth_token,omitempty" yaml:"auth_token,omitempty"` RefreshToken string `json:"refresh_token,omitempty" yaml:"refresh_token,omitempty"` ClientID string `json:"client_id,omitempty" yaml:"client_id,omitempty"` @@ -163,7 +172,11 @@ type ( ) // Profile returns the given profile, or nil if it does not exist. +// This is safe to call even if y is nil. func (y *RpkYaml) Profile(name string) *RpkProfile { + if y == nil { + return nil + } for i, p := range y.Profiles { if p.Name == name { return &y.Profiles[i] @@ -190,32 +203,80 @@ func (y *RpkYaml) MoveProfileToFront(p *RpkProfile) { y.Profiles = reordered } -// Auth returns the given auth, or nil if it does not exist. -func (y *RpkYaml) Auth(name string) *RpkCloudAuth { +// LookupAuth returns an RpkCloudAuth based on the org and kind. +func (y *RpkYaml) LookupAuth(org, kind string) *RpkCloudAuth { for i, a := range y.CloudAuths { - if a.Name == name { + k, _ := a.Kind() + if a.OrgID == org && string(k) == kind { return &y.CloudAuths[i] } } return nil } -// PushAuth pushes an auth to the front and returns the auth's name. -func (y *RpkYaml) PushAuth(a RpkCloudAuth) string { +// LookupAuthByName returns all auths with the given name. +// There will be multiple matching auths if a person is authenticated +// to multiple orgs that are named the same. +func (y *RpkYaml) LookupAuthByName(name string) []*RpkCloudAuth { + var matches []*RpkCloudAuth + for i := range y.CloudAuths { + if y.CloudAuths[i].Name == name { + matches = append(matches, &y.CloudAuths[i]) + } + } + return matches +} + +// PushNewAuth pushes an auth to the front and sets it as the current auth. +func (y *RpkYaml) PushNewAuth(a RpkCloudAuth) { y.CloudAuths = append([]RpkCloudAuth{a}, y.CloudAuths...) - return a.Name + y.CurrentCloudAuthOrgID = a.OrgID + k, _ := a.Kind() + y.CurrentCloudAuthKind = string(k) } -// MoveAuthToFront moves the given auth to the front of the list. -func (y *RpkYaml) MoveAuthToFront(a *RpkCloudAuth) { +// MakeAuthCurrent finds the given auth, moves it to the front, and updates +// the current cloud auth fields. This pointer must exist, if it does not, +// this function panics. +func (y *RpkYaml) MakeAuthCurrent(a *RpkCloudAuth) { reordered := []RpkCloudAuth{*a} + var found bool for i := range y.CloudAuths { if &y.CloudAuths[i] == a { + found = true continue } reordered = append(reordered, y.CloudAuths[i]) } + if !found { + panic("MakeAuthCurrent called with an auth that does not exist") + } y.CloudAuths = reordered + y.CurrentCloudAuthOrgID = a.OrgID + y.CurrentCloudAuthKind = string(a.AnyKind()) +} + +// DropAuth removes the given auth from the list of auths. If this was the +// current auth, this clears the current auth. +func (y *RpkYaml) DropAuth(a *RpkCloudAuth) { + dropped := y.CloudAuths[:0] + for i := range y.CloudAuths { + if &y.CloudAuths[i] == a { + continue + } + dropped = append(dropped, y.CloudAuths[i]) + } + y.CloudAuths = dropped + if y.CurrentCloudAuthOrgID == a.OrgID && y.CurrentCloudAuthKind == string(a.AnyKind()) { + y.CurrentCloudAuthOrgID = "" + y.CurrentCloudAuthKind = "" + } +} + +// CurrentAuth returns the auth corresponding to the current cloud auth, if +// it exists. +func (y *RpkYaml) CurrentAuth() *RpkCloudAuth { + return y.LookupAuth(y.CurrentCloudAuthOrgID, y.CurrentCloudAuthKind) } type CloudAuthKind string @@ -238,6 +299,13 @@ func (a *RpkCloudAuth) Kind() (CloudAuthKind, bool) { } } +// AnyKind returns the kind, or "uninitialized" if the kind is not known. +// This is the same as Kind but without a second return. +func (a *RpkCloudAuth) AnyKind() CloudAuthKind { + k, _ := a.Kind() + return k +} + /////////// // FUNCS // /////////// @@ -260,7 +328,7 @@ func (p *RpkProfile) Defaults() *RpkGlobals { // CurrentAuth returns the current cloud Auth. func (p *RpkProfile) CurrentAuth() *RpkCloudAuth { - return p.c.rpkYaml.Auth(p.c.rpkYaml.CurrentCloudAuth) + return p.c.rpkYaml.LookupAuth(p.c.rpkYaml.CurrentCloudAuthOrgID, p.c.rpkYaml.CurrentCloudAuthKind) } // DevOverrides returns any configured dev overrides. @@ -274,12 +342,42 @@ func (p *RpkProfile) HasSASLCredentials() bool { return s != nil && s.User != "" && s.Password != "" } +// ActualAuth returns the actual cloud auth for this profile, if it exists. +func (p *RpkProfile) ActualAuth() *RpkCloudAuth { + if p == nil { + return nil + } + return p.c.rpkYamlActual.LookupAuth(p.CloudCluster.AuthOrgID, p.CloudCluster.AuthKind) +} + +// VirtualAuth returns the virtual cloud auth for this profile if +// this profile is for a cloud cluster (following the newer scheme +// of the CloudCluster field). +func (p *RpkProfile) VirtualAuth() *RpkCloudAuth { + if p == nil { + return nil + } + return p.c.rpkYaml.LookupAuth(p.CloudCluster.AuthOrgID, p.CloudCluster.AuthKind) +} + // HasClientCredentials returns if both ClientID and ClientSecret are empty. func (a *RpkCloudAuth) HasClientCredentials() bool { k, _ := a.Kind() return k == CloudAuthClientCredentials } +// Equals returns if the two cloud auths are the same, which is true +// if the name and org ID match (ignoring all other values). +func (a *RpkCloudAuth) Equals(other *RpkCloudAuth) bool { + if a == nil && other == nil { + return true + } + if a == nil || other == nil { + return false + } + return a.Name == other.Name && a.OrgID == other.OrgID +} + // Returns if the raw config is the same as the one in memory. func (y *RpkYaml) isTheSameAsRawFile() bool { var init, final *RpkYaml @@ -328,6 +426,20 @@ func (y *RpkYaml) WriteAt(fs afero.Fs, path string) error { return rpkos.ReplaceFile(fs, path, b, 0o644) } +// FullName returns "namespace/cluster_name". +func (c *RpkCloudCluster) FullName() string { + return fmt.Sprintf("%s/%s", c.Namespace, c.ClusterName) +} + +// HasAuth returns if the cluster has the given auth. +// This is ok to call even if c is nil. +func (c *RpkCloudCluster) HasAuth(a RpkCloudAuth) bool { + if c == nil { + return false + } + return c.AuthOrgID == a.OrgID && c.AuthKind == string(a.AnyKind()) +} + //////////////// // MISC TYPES // //////////////// diff --git a/src/go/rpk/pkg/oauth/load.go b/src/go/rpk/pkg/oauth/load.go index 3c06538820e46..684bf930b3520 100644 --- a/src/go/rpk/pkg/oauth/load.go +++ b/src/go/rpk/pkg/oauth/load.go @@ -3,8 +3,11 @@ package oauth import ( "context" "fmt" + "time" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cloudapi" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/httpapi" rpkos "github.com/redpanda-data/redpanda/src/go/rpk/pkg/os" "github.com/spf13/afero" "go.uber.org/zap" @@ -13,45 +16,309 @@ import ( // LoadFlow loads or creates a config at default path, and validates and // refreshes or creates an auth token using the given authentication provider. // +// This returns the actual auth, the virtual auth, and any error. If the virtual +// auth is able to be loaded, it is always returned even if there is an error. +// // This function is expected to be called at the start of most commands, and it -// saves the token and client ID to the passed cloud config. -func LoadFlow(ctx context.Context, fs afero.Fs, cfg *config.Config, cl Client, noUI bool) (token string, err error) { +// saves the token and client ID to the passed cloud config. This returns the +// *actual* currently selected auth. +func LoadFlow(ctx context.Context, fs afero.Fs, cfg *config.Config, cl Client, noUI, forceReload bool, cloudAPIURL string) (authAct, authVir *config.RpkCloudAuth, clearedProfile, isNewAuth bool, err error) { // We want to avoid creating a root owned file. If the file exists, we // just chmod with rpkos.ReplaceFile and keep old perms even with sudo. // If the file does not exist, we will always be creating it to write // the token, so we fail if we are running with sudo. if _, ok := cfg.ActualRpkYaml(); ok && rpkos.IsRunningSudo() { - return "", fmt.Errorf("detected rpk is running with sudo; please execute this command without sudo to avoid saving the cloud configuration as a root owned file") + return nil, nil, false, false, fmt.Errorf("detected rpk is running with sudo; please execute this command without sudo to avoid saving the cloud configuration as a root owned file") } yVir := cfg.VirtualRpkYaml() - authVir := yVir.Auth(yVir.CurrentCloudAuth) // must exist - var resp Token + // We try logging into the org specified in the current profile. If + // the profile is not for a cloud cluster, we log into the default + // auth. + // + // We use the virtual auth to ensure we pick up flags (--secrets). + pVir := yVir.Profile(yVir.CurrentProfile) + authVir = pVir.VirtualAuth() + if authVir == nil { + authVir = yVir.CurrentAuth() // must be non-nil; we always have a default virtual auth + } + + var ( + tok Token + isNewToken bool + authKind string + ) if authVir.HasClientCredentials() { zap.L().Sugar().Debug("logging in using client credential flow") - resp, err = ClientCredentialFlow(ctx, cl, authVir) + tok, isNewToken, err = ClientCredentialFlow(ctx, cl, authVir, forceReload) + authKind = string(config.CloudAuthClientCredentials) } else { zap.L().Sugar().Debug("logging in using OAUTH flow") - resp, err = DeviceFlow(ctx, cl, authVir, noUI) + tok, isNewToken, err = DeviceFlow(ctx, cl, authVir, noUI, forceReload) + authKind = string(config.CloudAuthSSO) } if err != nil { - return "", fmt.Errorf("unable to retrieve a cloud token: %w", err) + return nil, authVir, false, false, fmt.Errorf("unable to retrieve a cloud token: %w", err) } - // We want to update the actual auth. yAct, err := cfg.ActualRpkYamlOrEmpty() if err != nil { - return "", fmt.Errorf("unable to load your rpk.yaml file: %v", err) + return nil, authVir, false, false, fmt.Errorf("unable to load your rpk.yaml file: %v", err) + } + + var ( + org cloudapi.Organization + orgErr error + orgOnce bool + getOrg = func() (cloudapi.Organization, error) { + if orgOnce { + return org, orgErr + } + orgOnce = true + org, orgErr = cloudapi.NewClient(cloudAPIURL, tok.AccessToken, httpapi.ReqTimeout(10*time.Second)).Organization(ctx) + if orgErr != nil { + return org, fmt.Errorf("unable to retrieve the organization for this token: %w", orgErr) + } + return org, orgErr + } + ) + + // If this is a new token and we have a cloud profile, we need to + // lookup the organization. The SSO flow may have logged into a + // different org than the current auth and if so, we need to clear + // the current profile. We do this same logic for client credentials + // this will be a no-op basically. + if pAuthAct := yAct.Profile(yAct.CurrentProfile).ActualAuth(); isNewToken && pAuthAct != nil { + org, orgErr := getOrg() + if orgErr != nil { + return nil, authVir, false, false, orgErr + } + if org.ID != pAuthAct.OrgID { + clearedProfile = true + yAct.CurrentProfile = "" + yVir.CurrentProfile = "" + } + } + + loggedAuth := *authVir + + // If this is a new token, if we had no profile, but we previously did + // log in, we need to check the same thing. For example, if we + // previously logged in via client credentials and did not create a + // profile, and now we're logging in again with new client credentials, + // we need to clear the current auth so we push new auth below. + if yAuthAct := yAct.CurrentAuth(); isNewToken && yAuthAct != nil { + org, orgErr := getOrg() + if orgErr != nil { + return nil, authVir, false, false, orgErr + } + if org.ID != yAuthAct.OrgID { + yAuthVir := yVir.CurrentAuth() + // The virtual auth here *should* have the same org and kind. + if yAuthAct.OrgID != yAuthVir.OrgID || yAuthAct.AnyKind() != yAuthVir.AnyKind() { + panic("params invariant: virtual auth != actual auth") + } + yAct.CurrentCloudAuthOrgID = "" + yVir.CurrentCloudAuthOrgID = "" + yAct.CurrentCloudAuthKind = "" + yVir.CurrentCloudAuthKind = "" + } } - authAct := yAct.Auth(yAct.CurrentCloudAuth) - if authAct == nil { - yAct.CurrentCloudAuth = yAct.PushAuth(config.DefaultRpkCloudAuth()) - authAct = yAct.Auth(yAct.CurrentCloudAuth) + + // We want to update the actual auth. + // + // Failure checking: + // + // 0 Virtual profile has auth, actual profile does not: this is + // not possible. We do not allow creating an auth struct from + // flags (only client ID / secret), so any virtual auth must + // be from actual auth. + // + // 1 Happy path: profile auth == current auth. We just update the + // current token. + // + // 2 Profile has auth, but it is not the current auth. This should + // not happen because we switch auth whenever the user switches + // profiles. If we see this, maybe the file was updated manually. + // We use the profile auth. + // + // 3 Profile has auth and the auth does not exist: maybe the file + // was edited manually. ActualAuth returns nil here, so the outdated + // auth will be overwritten. + // + // 4 Profile has no auth, rpk.yaml has current auth: the user previously + // logged in and had a cloud profile, but then switched to a SH cluster. + // We log into the rpk.yaml auth. + // + // 5 Profile has no auth, rpk.yaml has no auth: either the token is for + // a new org and auth was cleared just above, or the user has never + // logged in. We create a new auth, lookup the org, and push this + // new auth as current auth. + + pAct := yAct.Profile(yAct.CurrentProfile) + pAuthAct := pAct.ActualAuth() + yAuthAct := yAct.CurrentAuth() + + if pAuthAct != nil { + // SUMMARY: We make the current profile's auth the top level + // yaml's current auth, and ensure we are logging into the + // profile's auth. + // + // This is case 1 or 2, the logic is identical. We start with + // some case 2 logic: we know the auth actually exists, so we + // enforce it is the current rpk.yaml auth. + yAct.MakeAuthCurrent(pAuthAct) + + // Case 1 and 2: we check some invariants and then ensure the + // virtual rpk.yaml also has the same current auth. + pAuthVir := pVir.VirtualAuth() + if pAuthVir == nil { + panic("params invariant: virtual profile auth is nil even though actual profile auth is not") + } + if !pAuthVir.Equals(pAuthAct) { + panic("params invariant: internal virtual auth name/org != actual name/org") + } + yVir.MakeAuthCurrent(pAuthVir) + + // Finally, set authAct and authVir so they can be updated below. + authAct = pAuthAct + authVir = pAuthVir + } else { + // SUMMARY: Profile has no auth, or there is no auth at all in + // the rpk yaml. We log in, creating a new auth if necessary. + if pAct != nil { + // Case 3. This profile refers to deleted auth somehow. + // We will keep the cloud cluster name details, but the auth + // name is now useless. + // + // We can leave the profile and warn of problems on startup + // or via some lint command. + pAct.CloudCluster.AuthOrgID = "" + pAct.CloudCluster.AuthKind = "" + } + if yAuthAct == nil { + // Case 5. + yAct.PushNewAuth(config.DefaultRpkCloudAuth()) + yAuthAct = yAct.CurrentAuth() + isNewAuth = true + } + // authVir started as yAuthVir, but could have been cleared above + // if/when we cleared the yaml auth. + // + // If authVir was cleared, we need to reset the client ID and + // secret to what we used to log in with. + yAuthVir := yVir.CurrentAuth() + if yAuthVir == nil { + yVir.PushNewAuth(config.DefaultRpkCloudAuth()) + yAuthVir = yVir.CurrentAuth() + if loggedAuth.ClientID != "" { + yAuthVir.ClientID = loggedAuth.ClientID + } + if loggedAuth.ClientSecret != "" { + yAuthVir.ClientSecret = loggedAuth.ClientSecret + } + } + if !yAuthVir.Equals(yAuthAct) { + panic("params invariant: virtual auth != actual auth") + } + authAct = yAuthAct + authVir = yAuthVir } + + // If this is a new token, we need to lookup the organization. The SSO + // flow may have logged into a different org than the current auth, and + // if so, we need to push new auth AND swap away from the current + // profile. We do this same thing for client credentials because it is + // possible the user is passing flags to try swapping back to a prior + // login. + // + // If this is a new auth entirely (case 5), we also look up the org. + if isNewToken || isNewAuth { + org, orgErr := getOrg() + if orgErr != nil { + return nil, authVir, false, false, orgErr + } + + // If this is new default auth, we actually could have logged + // into an auth that was already logged into in the past, but + // is not the current auth. If this is the case, we delete our + // newly pushed current auth and instead make that old auth the + // current auth. + if isNewAuth { + check := func(y *config.RpkYaml, newAuth **config.RpkCloudAuth) { + var dropped bool + for i := range y.CloudAuths { + a := &y.CloudAuths[i] + if a.OrgID == org.ID && string(a.AnyKind()) == authKind { + y.DropAuth(*newAuth) + dropped = true + break + } + } + // If we found old auth, we dropped our new auth. + // Dropping auth shifts the array, so we re-lookup + // where the auth is. + if !dropped { + return + } + for i := range y.CloudAuths { + a := &y.CloudAuths[i] + if a.OrgID == org.ID && string(a.AnyKind()) == authKind { + *newAuth = a + y.MakeAuthCurrent(*newAuth) + return + } + } + } + check(yAct, &authAct) + check(yVir, &authVir) + } + + // We always write the auth name / org / orgID and update the + // current auth. This is a no-op if check just above did stuff. + + authVir.Organization = org.Name + authAct.Organization = org.Name + + authVir.OrgID = org.ID + authAct.OrgID = org.ID + + name := fmt.Sprintf("%s-%s %s", authVir.OrgID, authVir.AnyKind(), authVir.Organization) + + authVir.Name = name + authAct.Name = name + + yVir.CurrentCloudAuthOrgID = org.ID + yAct.CurrentCloudAuthOrgID = org.ID + + fmt.Println("updating currents, orgID", org.ID, "kind", authKind) + + yVir.CurrentCloudAuthKind = string(authVir.AnyKind()) + yAct.CurrentCloudAuthKind = string(authVir.AnyKind()) // we use the virtual kind here -- clientID is updated below + + } + + // We avoid copying the client secret, but we do keep the client ID. + authVir.AuthToken = tok.AccessToken + authAct.AuthToken = tok.AccessToken authAct.ClientID = authVir.ClientID - authAct.AuthToken = resp.AccessToken - authVir.AuthToken = resp.AccessToken - return resp.AccessToken, yAct.Write(fs) + return authAct, authVir, clearedProfile, isNewAuth, yAct.Write(fs) +} + +// PrintSwapMessage prints a message to the user about swapping away from a +// profile due to a new login. +func PrintSwapMessage(priorProfile *config.RpkProfile, newAuth *config.RpkCloudAuth) { + fmt.Printf("You are now logged into organization %q (%s).", newAuth.Organization, newAuth.OrgID) + priorAuth := priorProfile.ActualAuth() + fmt.Printf("rpk swapped away from your prior profile %q because that profile authenticates\nwith organization %q (%s).\n", priorProfile.Name, priorAuth.Organization, priorAuth.OrgID) +} + +// MaybePrintSwapMessage prints a message to the user about swapping away from a +// profile due to a new login, if the profile was cleared. +func MaybePrintSwapMessage(clearedProfile bool, priorProfile *config.RpkProfile, newAuth *config.RpkCloudAuth) { + if clearedProfile { + PrintSwapMessage(priorProfile, newAuth) + } } diff --git a/src/go/rpk/pkg/oauth/oauth.go b/src/go/rpk/pkg/oauth/oauth.go index dd544af4c62b8..c883220c2d487 100644 --- a/src/go/rpk/pkg/oauth/oauth.go +++ b/src/go/rpk/pkg/oauth/oauth.go @@ -9,7 +9,6 @@ import ( "github.com/lestrrat-go/jwx/jwt" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" - "go.uber.org/zap" ) type ( @@ -59,66 +58,81 @@ type ( // ClientCredentialFlow follows the OAuth 2.0 client credential authentication // flow. First it validates whether the configuration already have a valid // token. -func ClientCredentialFlow(ctx context.Context, cl Client, auth *config.RpkCloudAuth) (Token, error) { +func ClientCredentialFlow(ctx context.Context, cl Client, auth *config.RpkCloudAuth, forceReload bool) (Token, bool, error) { // We only validate the token if we have the client ID, if one of them is // not present we just start the login flow again. - if auth.AuthToken != "" && auth.ClientID != "" { + if auth.AuthToken != "" && auth.ClientID != "" && !forceReload { expired, err := ValidateToken(auth.AuthToken, cl.Audience(), auth.ClientID) if err != nil { - return Token{}, fmt.Errorf("unable to validate your authorization token: %v", err) + return Token{}, false, fmt.Errorf("unable to validate your authorization token: %v", err) } if !expired { - zap.L().Sugar().Debug("using existing authorization token") - return Token{AccessToken: auth.AuthToken}, nil + fmt.Println("Your existing auth token is still valid, avoiding re-authentication.") + return Token{AccessToken: auth.AuthToken}, false, nil } - zap.L().Sugar().Debug("authorization token expired. Requesting a new one") - + fmt.Println("Your existing authorization token has expired.") + } + fmt.Println("Requesting a new authorization token with your client credentials.") + t, err := cl.Token(ctx, auth.ClientID, auth.ClientSecret) + if err == nil { + fmt.Println("Successfully retrieved a new authorization token.") } - return cl.Token(ctx, auth.ClientID, auth.ClientSecret) + return t, true, err } // DeviceFlow follows the OAuth 2.0 device authentication flow. First it // validates whether the configuration already have a valid token. -func DeviceFlow(ctx context.Context, cl Client, auth *config.RpkCloudAuth, noUI bool) (Token, error) { +// +// This returns the token and if the token is new (or false if the token +// was still valid). +func DeviceFlow(ctx context.Context, cl Client, auth *config.RpkCloudAuth, noUI, forceReload bool) (Token, bool, error) { // We only validate the token if we have the client ID, if one of them is // not present we just start the login flow again. - if auth.AuthToken != "" && auth.ClientID != "" { + if auth.AuthToken != "" && auth.ClientID != "" && !forceReload { expired, err := ValidateToken(auth.AuthToken, cl.Audience(), auth.ClientID) if err != nil { - return Token{}, fmt.Errorf("unable to validate your authorization token: %v", err) + return Token{}, false, fmt.Errorf("unable to validate your authorization token: %v", err) } if !expired { - zap.L().Sugar().Debug("using existing authorization token") - return Token{AccessToken: auth.AuthToken}, nil + fmt.Println("Your existing auth token is still valid, avoiding re-authentication.") + return Token{AccessToken: auth.AuthToken}, false, nil } - zap.L().Sugar().Debug("authorization token expired. Requesting a new one") + fmt.Println("Your existing authorization token has expired.") } + fmt.Println("Requesting a new authorization token.") dcode, err := cl.DeviceCode(ctx) if err != nil { - return Token{}, fmt.Errorf("unable to request the device authorization: %w", err) + return Token{}, false, fmt.Errorf("unable to request the device authorization: %w", err) } if !isURL(dcode.VerificationURLComplete) { - return Token{}, fmt.Errorf("authorization server returned an invalid URL: %s; please contact Redpanda support", dcode.VerificationURLComplete) + return Token{}, false, fmt.Errorf("authorization server returned an invalid URL: %s; please contact Redpanda support", dcode.VerificationURLComplete) } if noUI { - fmt.Printf("For authentication, go to %q and log in.\n", dcode.VerificationURLComplete) + fmt.Printf("Please proceed to the following URL to login:\n\n %q\n\n", dcode.VerificationURLComplete) } else { - fmt.Printf("Opening your browser for authentication, if does not open automatically, please open %q and proceed to login.\n", dcode.VerificationURLComplete) + fmt.Printf(`Opening your browser for authentication. +If does not open automatically, please proceed to the following URL to login: + + %s + +`, dcode.VerificationURLComplete) err = cl.URLOpener(dcode.VerificationURLComplete) if err != nil { - return Token{}, fmt.Errorf("unable to open the web browser: %v; you may login using 'rpk cloud login --no-browser'", err) + return Token{}, false, fmt.Errorf("unable to open the web browser: %v; you may login using 'rpk cloud login --no-browser'", err) } } token, err := waitForDeviceToken(ctx, cl, dcode) if err != nil { - return Token{}, err + return Token{}, false, err + } else { + fmt.Println("Successfully retrieved a new authorization token.") } auth.ClientID = cl.AuthClientID() // if everything succeeded, save the clientID to the one used to generate the token - return token, nil + return token, true, nil } func waitForDeviceToken(ctx context.Context, cl Client, dcode DeviceCode) (Token, error) { diff --git a/src/go/rpk/pkg/out/out.go b/src/go/rpk/pkg/out/out.go index 643e19ce176ac..a759904eec534 100644 --- a/src/go/rpk/pkg/out/out.go +++ b/src/go/rpk/pkg/out/out.go @@ -23,20 +23,10 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/twmb/franz-go/pkg/kadm" - - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" ) func norm(header string) string { return strings.TrimSpace(strings.ToUpper(header)) } -// CheckExitCloudAdmin exits if the profile has FromCloud=true and no -// ALLOW_RPK_CLOUD_ADMIN override. -func CheckExitCloudAdmin(p *config.RpkProfile) { - if p.FromCloud && !p.DevOverrides().AllowRpkCloudAdmin { - Die("This admin API based command is not supported on Redpanda Cloud clusters.") - } -} - // Confirm prompts the user to confirm the formatted message and returns the // confirmation result or an error. func Confirm(msg string, args ...interface{}) (bool, error) {