From 96cc1ae8d1e0aa441db561142ea0b17fa1974c55 Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Tue, 14 Nov 2023 13:18:38 +0530 Subject: [PATCH] Cleanup RBAC commands Also add set-role command for user --- cmd/profile.go | 25 +++++----- cmd/role.go | 31 ++++++++---- cmd/stream.go | 24 ++++----- cmd/style.go | 31 ++++++------ cmd/user.go | 133 +++++++++++++++++++++++++++++++++++++++++-------- cmd/version.go | 12 ++--- main.go | 2 + 7 files changed, 179 insertions(+), 79 deletions(-) diff --git a/cmd/profile.go b/cmd/profile.go index f259b03..ea9b84f 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -20,6 +20,7 @@ import ( "fmt" "net/url" "os" + "pb/pkg/config" "pb/pkg/model/credential" "pb/pkg/model/defaultprofile" @@ -39,19 +40,19 @@ func (item *ProfileListItem) Render(highlight bool) string { if highlight { render := fmt.Sprintf( "%s\n%s\n%s", - selectedStyle.Render(item.title), - selectedStyleAlt.Render(fmt.Sprintf("url: %s", item.url)), - selectedStyleAlt.Render(fmt.Sprintf("user: %s", item.user)), + SelectedStyle.Render(item.title), + SelectedStyleAlt.Render(fmt.Sprintf("url: %s", item.url)), + SelectedStyleAlt.Render(fmt.Sprintf("user: %s", item.user)), ) - return selectedItemOuter.Render(render) + return SelectedItemOuter.Render(render) } render := fmt.Sprintf( "%s\n%s\n%s", - standardStyle.Render(item.title), - standardStyleAlt.Render(fmt.Sprintf("url: %s", item.url)), - standardStyleAlt.Render(fmt.Sprintf("user: %s", item.user)), + StandardStyle.Render(item.title), + StandardStyleAlt.Render(fmt.Sprintf("url: %s", item.url)), + StandardStyleAlt.Render(fmt.Sprintf("user: %s", item.user)), ) - return itemOuter.Render(render) + return ItemOuter.Render(render) } var AddProfileCmd = &cobra.Command{ @@ -140,9 +141,9 @@ var RemoveProfileCmd = &cobra.Command{ fileConfig.DefaultProfile = "" } config.WriteConfigToFile(fileConfig) - fmt.Printf("Deleted profile %s\n", styleBold.Render(name)) + fmt.Printf("Deleted profile %s\n", StyleBold.Render(name)) } else { - fmt.Printf("No profile found with the name: %s", styleBold.Render(name)) + fmt.Printf("No profile found with the name: %s", StyleBold.Render(name)) } return nil @@ -185,12 +186,12 @@ var DefaultProfileCmd = &cobra.Command{ fileConfig.DefaultProfile = name } else { name = lipgloss.NewStyle().Bold(true).Render(name) - err := fmt.Sprintf("profile %s does not exist", styleBold.Render(name)) + err := fmt.Sprintf("profile %s does not exist", StyleBold.Render(name)) return errors.New(err) } config.WriteConfigToFile(fileConfig) - fmt.Printf("%s is now set as default profile\n", styleBold.Render(name)) + fmt.Printf("%s is now set as default profile\n", StyleBold.Render(name)) return nil }, } diff --git a/cmd/role.go b/cmd/role.go index e62dad1..3bd3b91 100644 --- a/cmd/role.go +++ b/cmd/role.go @@ -21,10 +21,11 @@ import ( "fmt" "io" "os" - "pb/pkg/model/role" "strings" "sync" + "pb/pkg/model/role" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" @@ -42,18 +43,18 @@ type RoleData struct { func (user *RoleData) Render() string { var s strings.Builder - s.WriteString(standardStyle.Render("Privilege: ")) - s.WriteString(standardStyleAlt.Render(user.Privilege)) + s.WriteString(StandardStyle.Render("Privilege: ")) + s.WriteString(StandardStyleAlt.Render(user.Privilege)) s.WriteString("\n") if user.Resource != nil { if user.Resource.Stream != "" { - s.WriteString(standardStyle.Render("Stream: ")) - s.WriteString(standardStyleAlt.Render(user.Resource.Stream)) + s.WriteString(StandardStyle.Render("Stream: ")) + s.WriteString(StandardStyleAlt.Render(user.Resource.Stream)) s.WriteString("\n") } if user.Resource.Tag != "" { - s.WriteString(standardStyle.Render("Tag: ")) - s.WriteString(standardStyleAlt.Render(user.Resource.Tag)) + s.WriteString(StandardStyle.Render("Tag: ")) + s.WriteString(StandardStyleAlt.Render(user.Resource.Tag)) s.WriteString("\n") } } @@ -69,6 +70,17 @@ var AddRoleCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { name := args[0] + // check if the role already exists + var roles []string + client := DefaultClient() + if err := fetchRoles(&client, &roles); err != nil { + return err + } + if strings.Contains(strings.Join(roles, " "), name) { + fmt.Println("role already exists, please use a different name") + return nil + } + _m, err := tea.NewProgram(role.New()).Run() if err != nil { fmt.Printf("there's been an error: %v", err) @@ -113,7 +125,6 @@ var AddRoleCmd = &cobra.Command{ putBody = bytes.NewBuffer(roleDataJSON) } - client := DefaultClient() req, err := client.NewRequest("PUT", "role/"+name, putBody) if err != nil { return err @@ -161,7 +172,7 @@ var RemoveRoleCmd = &cobra.Command{ } if resp.StatusCode == 200 { - fmt.Printf("Removed role %s\n", styleBold.Render(name)) + fmt.Printf("Removed role %s\n", StyleBold.Render(name)) } else { bytes, err := io.ReadAll(resp.Body) if err != nil { @@ -211,7 +222,7 @@ var ListRoleCmd = &cobra.Command{ for idx, roleName := range roles { fetchRes := roleResponses[idx] fmt.Print("• ") - fmt.Println(standardStyleBold.Bold(true).Render(roleName)) + fmt.Println(StandardStyleBold.Bold(true).Render(roleName)) if fetchRes.err == nil { for _, role := range fetchRes.data { fmt.Println(lipgloss.NewStyle().PaddingLeft(3).Render(role.Render())) diff --git a/cmd/stream.go b/cmd/stream.go index f19ecc2..e26818d 100644 --- a/cmd/stream.go +++ b/cmd/stream.go @@ -48,8 +48,8 @@ type StreamListItem struct { } func (item *StreamListItem) Render() string { - render := standardStyle.Render(item.name) - return itemOuter.Render(render) + render := StandardStyle.Render(item.name) + return ItemOuter.Render(render) } // StreamRetentionData is the data structure for stream retention @@ -111,7 +111,7 @@ var AddStreamCmd = &cobra.Command{ } if resp.StatusCode == 200 { - fmt.Printf("Created stream %s\n", styleBold.Render(name)) + fmt.Printf("Created stream %s\n", StyleBold.Render(name)) } else { bytes, err := io.ReadAll(resp.Body) if err != nil { @@ -152,7 +152,7 @@ var StatStreamCmd = &cobra.Command{ isRetentionSet := len(retention) > 0 - fmt.Println(styleBold.Render("\nInfo:")) + fmt.Println(StyleBold.Render("\nInfo:")) fmt.Printf(" Event Count: %d\n", ingestionCount) fmt.Printf(" Ingestion Size: %s\n", humanize.Bytes(uint64(ingestionSize))) fmt.Printf(" Storage Size: %s\n", humanize.Bytes(uint64(storageSize))) @@ -162,14 +162,14 @@ var StatStreamCmd = &cobra.Command{ fmt.Println() if isRetentionSet { - fmt.Println(styleBold.Render("Retention:")) + fmt.Println(StyleBold.Render("Retention:")) for _, item := range retention { - fmt.Printf(" Action: %s\n", styleBold.Render(item.Action)) - fmt.Printf(" Duration: %s\n", styleBold.Render(item.Duration)) + fmt.Printf(" Action: %s\n", StyleBold.Render(item.Action)) + fmt.Printf(" Duration: %s\n", StyleBold.Render(item.Duration)) fmt.Println() } } else { - fmt.Println(styleBold.Render("No retention period set on stream\n")) + fmt.Println(StyleBold.Render("No retention period set on stream\n")) } alertsData, err := fetchAlerts(&client, name) @@ -181,9 +181,9 @@ var StatStreamCmd = &cobra.Command{ isAlertsSet := len(alerts) > 0 if isAlertsSet { - fmt.Println(styleBold.Render("Alerts:")) + fmt.Println(StyleBold.Render("Alerts:")) for _, alert := range alerts { - fmt.Printf(" Alert: %s\n", styleBold.Render(alert.Name)) + fmt.Printf(" Alert: %s\n", StyleBold.Render(alert.Name)) ruleFmt := fmt.Sprintf( "%s %s %s repeated %d times", alert.Rule.Config.Column, @@ -199,7 +199,7 @@ var StatStreamCmd = &cobra.Command{ fmt.Print("\n\n") } } else { - fmt.Println(styleBold.Render("No alerts set on stream\n")) + fmt.Println(StyleBold.Render("No alerts set on stream\n")) } return nil @@ -226,7 +226,7 @@ var RemoveStreamCmd = &cobra.Command{ } if resp.StatusCode == 200 { - fmt.Printf("Removed stream %s\n", styleBold.Render(name)) + fmt.Printf("Removed stream %s\n", StyleBold.Render(name)) } else { bytes, err := io.ReadAll(resp.Body) if err != nil { diff --git a/cmd/style.go b/cmd/style.go index dea7e61..8bfd183 100644 --- a/cmd/style.go +++ b/cmd/style.go @@ -22,21 +22,18 @@ import ( // styling for cli outputs var ( - FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"} - FocusSecondry = lipgloss.AdaptiveColor{Light: "18", Dark: "220"} - - standardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"} - standardSecondry = lipgloss.AdaptiveColor{Light: "238", Dark: "254"} - - standardStyle = lipgloss.NewStyle().Foreground(standardPrimary) - standardStyleBold = lipgloss.NewStyle().Foreground(standardPrimary).Bold(true) - standardStyleAlt = lipgloss.NewStyle().Foreground(standardSecondry) - - selectedStyle = lipgloss.NewStyle().Foreground(FocusPrimary).Bold(true) - selectedStyleAlt = lipgloss.NewStyle().Foreground(FocusSecondry) - - selectedItemOuter = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderLeft(true).PaddingLeft(1).BorderForeground(FocusPrimary) - itemOuter = lipgloss.NewStyle().PaddingLeft(1) - - styleBold = lipgloss.NewStyle().Bold(true) + FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"} + FocusSecondary = lipgloss.AdaptiveColor{Light: "18", Dark: "220"} + + StandardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"} + StandardSecondary = lipgloss.AdaptiveColor{Light: "238", Dark: "254"} + StandardStyle = lipgloss.NewStyle().Foreground(StandardPrimary) + StandardStyleBold = lipgloss.NewStyle().Foreground(StandardPrimary).Bold(true) + StandardStyleAlt = lipgloss.NewStyle().Foreground(StandardSecondary) + SelectedStyle = lipgloss.NewStyle().Foreground(FocusPrimary).Bold(true) + SelectedStyleAlt = lipgloss.NewStyle().Foreground(FocusSecondary) + SelectedItemOuter = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderLeft(true).PaddingLeft(1).BorderForeground(FocusPrimary) + ItemOuter = lipgloss.NewStyle().PaddingLeft(1) + + StyleBold = lipgloss.NewStyle().Bold(true) ) diff --git a/cmd/user.go b/cmd/user.go index 73fd4b4..93dc8c9 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -20,11 +20,9 @@ import ( "encoding/json" "fmt" "io" - "os" - "pb/pkg/model/role" + "strings" "sync" - tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" "golang.org/x/exp/slices" @@ -37,7 +35,12 @@ type UserData struct { type UserRoleData map[string][]RoleData -var AddUserCmd = &cobra.Command{ +var ( + roleFlag = "role" + roleFlagShort = "r" +) + +var addUser = &cobra.Command{ Use: "add user-name", Example: " pb user add bob", Short: "Add a new user", @@ -58,25 +61,30 @@ var AddUserCmd = &cobra.Command{ return nil } - _m, err := tea.NewProgram(role.New()).Run() - if err != nil { - fmt.Printf("Alas, there's been an error: %v", err) - os.Exit(1) - } - m := _m.(role.Model) - - roles := m.Selection.Value() + // fetch all the roles to be applied to this user + rolesToSet := cmd.Flag(roleFlag).Value.String() + rolesToSetArr := strings.Split(rolesToSet, ",") - if !m.Success { - fmt.Println("aborted by user") - return nil + // fetch the role names on the server + var rolesOnServer []string + if err := fetchRoles(&client, &rolesOnServer); err != nil { + return err + } + rolesOnServerArr := strings.Join(rolesOnServer, " ") + + // validate if roles to be applied are actually present on the server + for idx, role := range rolesToSetArr { + rolesToSetArr[idx] = strings.TrimSpace(role) + if !strings.Contains(rolesOnServerArr, rolesToSetArr[idx]) { + fmt.Printf("role %s doesn't exist, please create a role using `pb role add %s`\n", rolesToSetArr[idx], rolesToSetArr[idx]) + return nil + } } var putBody io.Reader - roleDataJSON, _ := json.Marshal([]string{roles}) - putBody = bytes.NewBuffer(roleDataJSON) - - req, err := client.NewRequest("PUT", "user/"+name, putBody) + putBodyJson, _ := json.Marshal(rolesToSetArr) + putBody = bytes.NewBuffer([]byte(putBodyJson)) + req, err := client.NewRequest("POST", "user/"+name, putBody) if err != nil { return err } @@ -94,7 +102,7 @@ var AddUserCmd = &cobra.Command{ defer resp.Body.Close() if resp.StatusCode == 200 { - fmt.Printf("Added user %s \nPassword is: %s\n", name, body) + fmt.Printf("Added user: %s \nPassword is: %s\nRole(s) assigned: %s\n", name, body, rolesToSet) } else { fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) } @@ -103,6 +111,11 @@ var AddUserCmd = &cobra.Command{ }, } +var AddUserCmd = func() *cobra.Command { + addUser.Flags().StringP(roleFlag, roleFlagShort, "", "specify the role(s) to be assigned to the user. Use comma separated values for multiple roles. Example: --role admin,developer") + return addUser +}() + var RemoveUserCmd = &cobra.Command{ Use: "remove user-name", Aliases: []string{"rm"}, @@ -123,7 +136,7 @@ var RemoveUserCmd = &cobra.Command{ } if resp.StatusCode == 200 { - fmt.Printf("Removed user %s\n", styleBold.Render(name)) + fmt.Printf("Removed user %s\n", StyleBold.Render(name)) } else { bytes, err := io.ReadAll(resp.Body) if err != nil { @@ -139,6 +152,82 @@ var RemoveUserCmd = &cobra.Command{ }, } +var SetUserRoleCmd = &cobra.Command{ + Use: "set-role user-name roles", + Short: "Set roles for a user", + Example: " pb user set-role bob admin,developer", + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return fmt.Errorf("requires at least 2 arguments") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + name := args[0] + + client := DefaultClient() + users, err := fetchUsers(&client) + if err != nil { + return err + } + + if !slices.ContainsFunc(users, func(user UserData) bool { + return user.ID == name + }) { + fmt.Printf("user doesn't exist. Please create the user with `pb user add %s`\n", name) + return nil + } + + // fetch all the roles to be applied to this user + rolesToSet := args[1] + rolesToSetArr := strings.Split(rolesToSet, ",") + + // fetch the role names on the server + var rolesOnServer []string + if err := fetchRoles(&client, &rolesOnServer); err != nil { + return err + } + rolesOnServerArr := strings.Join(rolesOnServer, " ") + + // validate if roles to be applied are actually present on the server + for idx, role := range rolesToSetArr { + rolesToSetArr[idx] = strings.TrimSpace(role) + if !strings.Contains(rolesOnServerArr, rolesToSetArr[idx]) { + fmt.Printf("role %s doesn't exist, please create a role using `pb role add %s`\n", rolesToSetArr[idx], rolesToSetArr[idx]) + return nil + } + } + + var putBody io.Reader + putBodyJson, _ := json.Marshal(rolesToSetArr) + putBody = bytes.NewBuffer([]byte(putBodyJson)) + req, err := client.NewRequest("PUT", "user/"+name+"/role", putBody) + if err != nil { + return err + } + + resp, err := client.client.Do(req) + if err != nil { + return err + } + + bytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + body := string(bytes) + defer resp.Body.Close() + + if resp.StatusCode == 200 { + fmt.Printf("Added role(s) %s to user %s\n", rolesToSet, name) + } else { + fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) + } + + return nil + }, +} + var ListUserCmd = &cobra.Command{ Use: "list", Short: "List all users", @@ -173,7 +262,7 @@ var ListUserCmd = &cobra.Command{ for idx, user := range users { roles := roleResponses[idx] fmt.Print("• ") - fmt.Println(standardStyleBold.Bold(true).Render(user.ID)) + fmt.Println(StandardStyleBold.Bold(true).Render(user.ID)) if roles.err == nil { for role := range roles.data { fmt.Println(lipgloss.NewStyle().PaddingLeft(3).Render(role)) diff --git a/cmd/version.go b/cmd/version.go index 65ea42f..a550f59 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -33,9 +33,9 @@ var VersionCmd = &cobra.Command{ func PrintVersion(version, commit string) { client := DefaultClient() - fmt.Printf("\n%s \n", standardStyleAlt.Render("pb version")) - fmt.Printf("- %s %s\n", standardStyleBold.Render("version: "), version) - fmt.Printf("- %s %s\n\n", standardStyleBold.Render("commit: "), commit) + fmt.Printf("\n%s \n", StandardStyleAlt.Render("pb version")) + fmt.Printf("- %s %s\n", StandardStyleBold.Render("version: "), version) + fmt.Printf("- %s %s\n\n", StandardStyleBold.Render("commit: "), commit) if err := PreRun(); err != nil { return @@ -45,7 +45,7 @@ func PrintVersion(version, commit string) { return } - fmt.Printf("%s %s \n", standardStyleAlt.Render("Connected to"), standardStyleBold.Render(DefaultProfile.URL)) - fmt.Printf("- %s %s\n", standardStyleBold.Render("version: "), about.Version) - fmt.Printf("- %s %s\n\n", standardStyleBold.Render("commit: "), about.Commit) + fmt.Printf("%s %s \n", StandardStyleAlt.Render("Connected to"), StandardStyleBold.Render(DefaultProfile.URL)) + fmt.Printf("- %s %s\n", StandardStyleBold.Render("version: "), about.Version) + fmt.Printf("- %s %s\n\n", StandardStyleBold.Render("commit: "), about.Commit) } diff --git a/main.go b/main.go index fddf2c7..1c4b409 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ package main import ( "errors" "os" + "pb/cmd" "pb/pkg/config" @@ -94,6 +95,7 @@ func main() { user.AddCommand(cmd.AddUserCmd) user.AddCommand(cmd.RemoveUserCmd) user.AddCommand(cmd.ListUserCmd) + user.AddCommand(cmd.SetUserRoleCmd) role.AddCommand(cmd.AddRoleCmd) role.AddCommand(cmd.RemoveRoleCmd)