Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 58 additions & 21 deletions cmd/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)

type RoleResource struct {
Stream string `json:"stream,omitempty"`
Dataset string `json:"dataset,omitempty"`
Tag string `json:"tag,omitempty"`
Stream string `json:"stream,omitempty"` // Legacy response compatibility.
}

type RoleData struct {
Expand All @@ -47,9 +50,14 @@ func (user *RoleData) Render() string {
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))
if dataset := roleDataset(*user.Resource); dataset != "" {
s.WriteString(StandardStyle.Render("Dataset: "))
s.WriteString(StandardStyleAlt.Render(dataset))
s.WriteString("\n")
}
if user.Resource.Tag != "" {
s.WriteString(StandardStyle.Render("Tag: "))
s.WriteString(StandardStyleAlt.Render(user.Resource.Tag))
s.WriteString("\n")
}
}
Expand Down Expand Up @@ -78,7 +86,7 @@ var AddRoleCmd = &cobra.Command{
return err
}

if strings.Contains(strings.Join(roles, " "), name) {
if slices.Contains(roles, name) {
fmt.Println("role already exists, please use a different name")
return nil
}
Expand All @@ -91,25 +99,19 @@ var AddRoleCmd = &cobra.Command{

m := _m.(role.Model)
privilege := m.Selection.Value()
stream := m.Stream.Value()

if !m.Success {
fmt.Println("aborted by user")
return nil
}

var putBody io.Reader
if privilege != "none" {
roleData := RoleData{Privilege: privilege}
switch privilege {
case "writer", "ingestor":
roleData.Resource = &RoleResource{Stream: stream}
case "reader":
roleData.Resource = &RoleResource{Stream: stream}
}
roleDataJSON, _ := json.Marshal([]RoleData{roleData})
putBody = bytes.NewBuffer(roleDataJSON)
roleData := newRoleData(privilege)
roleDataJSON, err := json.Marshal([]RoleData{roleData})
if err != nil {
cmd.Annotations["errors"] = fmt.Sprintf("Error encoding role: %s", err.Error())
return err
}
var putBody io.Reader = bytes.NewBuffer(roleDataJSON)

req, err := client.NewRequest("PUT", "role/"+name, putBody)
if err != nil {
Expand Down Expand Up @@ -157,6 +159,17 @@ var RemoveRoleCmd = &cobra.Command{

name := args[0]
client := internalHTTP.DefaultClient(&DefaultProfile)
var roles []string
if err := fetchRoles(&client, &roles); err != nil {
cmd.Annotations["errors"] = fmt.Sprintf("Error fetching roles: %s", err.Error())
return err
}
if !slices.Contains(roles, name) {
fmt.Println(missingRoleMessage(name, roles))
cmd.Annotations["errors"] = fmt.Sprintf("role %s does not exist", name)
return nil
}

req, err := client.NewRequest("DELETE", "role/"+name, nil)
if err != nil {
cmd.Annotations["errors"] = fmt.Sprintf("Error creating delete request: %s", err.Error())
Expand Down Expand Up @@ -272,7 +285,7 @@ func printRoleTable(roles []string, roleResponses []struct {

roleWidth := lipgloss.Width("ROLE")
privilegeWidth := lipgloss.Width("PRIVILEGE")
streamWidth := lipgloss.Width("STREAM")
streamWidth := lipgloss.Width("DATASET")

for idx, roleName := range roles {
roleW := lipgloss.Width(roleName)
Expand Down Expand Up @@ -337,7 +350,7 @@ func printRoleTable(roles []string, roleResponses []struct {
fmt.Printf("%s%s%s\n",
headerStyle.Render(padRight("ROLE", privilegeColumn+1)),
headerStyle.Render(padRight("PRIVILEGE", privilegeWidth+5)),
headerStyle.Render("STREAM"),
headerStyle.Render("DATASET"),
)
fmt.Printf("%s%s%s\n",
ruleStyle.Render(strings.Repeat("─", privilegeColumn+1)),
Expand Down Expand Up @@ -380,10 +393,34 @@ func printRoleTable(roles []string, roleResponses []struct {
}

func roleStream(action RoleData) string {
if action.Resource == nil || action.Resource.Stream == "" {
if action.Resource == nil {
return "-"
}
return action.Resource.Stream
if dataset := roleDataset(*action.Resource); dataset != "" {
return dataset
}
return "-"
}

func roleDataset(resource RoleResource) string {
if resource.Dataset != "" {
return resource.Dataset
}
return resource.Stream
}

func newRoleData(privilege string) RoleData {
return RoleData{Privilege: privilege}
}

func missingRoleMessage(name string, roles []string) string {
if len(roles) == 0 {
return fmt.Sprintf("role %s doesn't exist. No roles are available", name)
}

roleNames := append([]string(nil), roles...)
slices.Sort(roleNames)
return fmt.Sprintf("role %s doesn't exist. Available role names: %s", name, strings.Join(roleNames, ", "))
}

func orDash(value string) string {
Expand Down
65 changes: 49 additions & 16 deletions cmd/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"encoding/json"
"fmt"
"io"
"net/http"
"sort"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -70,6 +72,11 @@ var addUser = &cobra.Command{
}()

name := args[0]
if DefaultProfile.Cloud {
fmt.Println(cloudAddUserMessage())
cmd.Annotations["error"] = "user creation is not supported for cloud profiles"
return nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

client := internalHTTP.DefaultClient(&DefaultProfile)
users, err := fetchUsers(&client)
Expand All @@ -87,22 +94,24 @@ var addUser = &cobra.Command{
}

// fetch all the roles to be applied to this user
rolesToSet := cmd.Flag(roleFlag).Value.String()
rolesToSetArr := strings.Split(rolesToSet, ",")

// fetch the role names on the server
rolesToSet := strings.TrimSpace(cmd.Flag(roleFlag).Value.String())
var rolesOnServer []string
if err := fetchRoles(&client, &rolesOnServer); err != nil {
cmd.Annotations["error"] = err.Error()
return err
}
rolesOnServerArr := strings.Join(rolesOnServer, " ")
if rolesToSet == "" {
fmt.Println(selfHostedAddUserRoleMessage(name, rolesOnServer))
cmd.Annotations["error"] = "at least one role is required"
return nil
}
rolesToSetArr := strings.Split(rolesToSet, ",")

// 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])
if !slices.Contains(rolesOnServer, rolesToSetArr[idx]) {
fmt.Printf("role %s doesn't exist. Create it first with `pb role add %s`, or use an existing role with `pb user add %s --role <role>`\n", rolesToSetArr[idx], rolesToSetArr[idx], name)
cmd.Annotations["error"] = fmt.Sprintf("role %s doesn't exist", rolesToSetArr[idx])
return nil
}
Expand Down Expand Up @@ -190,7 +199,7 @@ var RemoveUserCmd = &cobra.Command{

var SetUserRoleCmd = &cobra.Command{
Use: "set-role user-name roles",
Short: "Set roles for a user",
Short: "Add roles to a user",
Example: " pb user set-role bob admin,developer",
PreRunE: func(_ *cobra.Command, args []string) error {
if len(args) < 2 {
Expand All @@ -216,7 +225,7 @@ var SetUserRoleCmd = &cobra.Command{
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)
fmt.Println(missingUserMessage(name, DefaultProfile.Cloud))
cmd.Annotations["error"] = "user does not exist"
return nil
}
Expand All @@ -228,21 +237,16 @@ var SetUserRoleCmd = &cobra.Command{
cmd.Annotations["error"] = err.Error()
return err
}
rolesOnServerArr := strings.Join(rolesOnServer, " ")

for idx, role := range rolesToSetArr {
rolesToSetArr[idx] = strings.TrimSpace(role)
if !strings.Contains(rolesOnServerArr, rolesToSetArr[idx]) {
if !slices.Contains(rolesOnServer, rolesToSetArr[idx]) {
fmt.Printf("role %s doesn't exist, please create a role using `pb role add %s`\n", rolesToSetArr[idx], rolesToSetArr[idx])
cmd.Annotations["error"] = fmt.Sprintf("role %s doesn't exist", 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)
req, err := newAddUserRolesRequest(&client, name, rolesToSetArr)
if err != nil {
cmd.Annotations["error"] = err.Error()
return err
Expand Down Expand Up @@ -274,6 +278,35 @@ var SetUserRoleCmd = &cobra.Command{
},
}

func newAddUserRolesRequest(client *internalHTTP.HTTPClient, name string, roles []string) (*http.Request, error) {
body, err := json.Marshal(roles)
if err != nil {
return nil, err
}
return client.NewRequest(http.MethodPatch, "user/"+name+"/role/add", bytes.NewReader(body))
}

func missingUserMessage(name string, cloud bool) string {
if cloud {
return fmt.Sprintf("user doesn't exist. Please invite the user from the Parseable Cloud dashboard first, then set the role with `pb user set-role %s <role>`", name)
}
return fmt.Sprintf("user doesn't exist. Please create the user with `pb user add %s`", name)
}

func cloudAddUserMessage() string {
return "`pb user add` is not available for Parseable Cloud. Please invite the user from the Parseable Cloud dashboard"
}

func selfHostedAddUserRoleMessage(name string, roles []string) string {
if len(roles) == 0 {
return fmt.Sprintf("a role is required to create user %s. No roles are available. Create one first with `pb role add <role>`, then run `pb user add %s --role <role>`", name, name)
}

roleNames := append([]string(nil), roles...)
sort.Strings(roleNames)
return fmt.Sprintf("a role is required to create user %s.\nAvailable roles: %s\nAssign one, for example: `pb user add %s --role %s`", name, strings.Join(roleNames, ", "), name, roleNames[0])
}

var ListUserCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Expand Down
Loading
Loading