Skip to content

Commit

Permalink
rpk: profile & cloud login rework
Browse files Browse the repository at this point in the history
SMALL
* Adds "rpk" to the early-check-exit error message; if a person has some
  rpk command in their bashrc, it is useful to know why rpk startup is
  failing

LARGE
* Reworks logins and profiles to be more explicit about the
  organizations being used
  • Loading branch information
twmb committed Mar 11, 2024
1 parent a5292a6 commit 117c58a
Show file tree
Hide file tree
Showing 116 changed files with 1,092 additions and 652 deletions.
2 changes: 1 addition & 1 deletion src/go/rpk/pkg/cli/acl/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/go/rpk/pkg/cli/acl/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/go/rpk/pkg/cli/acl/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/go/rpk/pkg/cli/acl/user/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/go/rpk/pkg/cli/acl/user/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/go/rpk/pkg/cli/acl/user/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/go/rpk/pkg/cli/acl/user/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions src/go/rpk/pkg/cli/cloud/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
87 changes: 8 additions & 79 deletions src/go/rpk/pkg/cli/cloud/auth/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
112 changes: 79 additions & 33 deletions src/go/rpk/pkg/cli/cloud/auth/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -19,59 +20,104 @@ 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 {
out.Die("rpk.yaml file does not exist")
}

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
}
57 changes: 7 additions & 50 deletions src/go/rpk/pkg/cli/cloud/auth/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit 117c58a

Please sign in to comment.