From 07329acf8b4008328e45dfeaa9656b71dbfe5d23 Mon Sep 17 00:00:00 2001 From: Samir F Date: Wed, 11 Sep 2024 11:32:31 -0400 Subject: [PATCH] Moving User commands of tools->orgs to tools->orgs->users (#293) Fixes #272 --- cli/tools/auth_service_accounts.go | 9 +- cli/tools/auth_tokens.go | 10 +- cli/tools/org_users.go | 192 ++++++++++++++++++ cli/tools/organizations.go | 143 +------------ cli/tools/tools.go | 9 + .../content/docs/usage_guide/tools_guide.md | 13 ++ 6 files changed, 225 insertions(+), 151 deletions(-) create mode 100644 cli/tools/org_users.go diff --git a/cli/tools/auth_service_accounts.go b/cli/tools/auth_service_accounts.go index a0cd1971..99f48a60 100644 --- a/cli/tools/auth_service_accounts.go +++ b/cli/tools/auth_service_accounts.go @@ -3,11 +3,12 @@ package tools import ( "context" "errors" + "fmt" "log" "log/slog" - "slices" "sort" "strconv" + "strings" "github.com/bep/simplecobra" "github.com/esnet/gdg/cli/support" @@ -148,7 +149,7 @@ func newServiceAccount() simplecobra.Commander { CommandsList: []simplecobra.Commander{}, RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { if len(args) < 2 { - return errors.New("requires a key name and a role('admin','viewer','editor') [ttl optional] ") + return fmt.Errorf("requires a key name and a role(%s) [ttl optional]", strings.Join(getBasicRoles(), ", ")) } name := args[0] role := args[1] @@ -166,8 +167,8 @@ func newServiceAccount() simplecobra.Commander { expiration = 0 } - if !slices.Contains([]string{"admin", "editor", "viewer"}, role) { - log.Fatal("Invalid role specified") + if !validBasicRole(role) { + log.Fatalf("Invalid role specified, '%s'. Valid roles are:[%s]", role, strings.Join(getBasicRoles(), ", ")) } serviceAcct, err := rootCmd.GrafanaSvc().CreateServiceAccount(name, role, expiration) if err != nil { diff --git a/cli/tools/auth_tokens.go b/cli/tools/auth_tokens.go index 1b2a2a95..0da13710 100644 --- a/cli/tools/auth_tokens.go +++ b/cli/tools/auth_tokens.go @@ -2,12 +2,12 @@ package tools import ( "context" - "errors" + "fmt" "log" "log/slog" - "slices" "sort" "strconv" + "strings" "github.com/bep/simplecobra" "github.com/esnet/gdg/cli/support" @@ -101,7 +101,7 @@ func newNewTokenCmd() simplecobra.Commander { CommandsList: []simplecobra.Commander{}, RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { if len(args) < 2 { - return errors.New("requires a key name and a role('admin','viewer','editor') [ttl optional] ") + return fmt.Errorf("requires a key name and a role(%s) [ttl optional] ", strings.Join(getBasicRoles(), ", ")) } name := args[0] role := args[1] @@ -119,8 +119,8 @@ func newNewTokenCmd() simplecobra.Commander { expiration = 0 } - if !slices.Contains([]string{"admin", "editor", "viewer"}, role) { - log.Fatal("Invalid role specified") + if !validBasicRole(role) { + log.Fatalf("Invalid role specified, '%s'. Valid roles are:[%s]", role, strings.Join(getBasicRoles(), ", ")) } key, err := rootCmd.GrafanaSvc().CreateAPIKey(name, role, expiration) if err != nil { diff --git a/cli/tools/org_users.go b/cli/tools/org_users.go new file mode 100644 index 00000000..9be1161b --- /dev/null +++ b/cli/tools/org_users.go @@ -0,0 +1,192 @@ +package tools + +import ( + "context" + "errors" + "fmt" + "log" + "log/slog" + "strconv" + "strings" + + "github.com/bep/simplecobra" + "github.com/esnet/gdg/cli/support" + "github.com/esnet/gdg/internal/config" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" +) + +func newOrgUsersCommand() simplecobra.Commander { + return &support.SimpleCommand{ + NameP: "users", + Short: "Manage organization users", + Long: "Manager organization users", + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + return cd.CobraCommand.Help() + }, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"user"} + }, + CommandsList: []simplecobra.Commander{ + newListUsers(), + newAddUserRoleCmd(), + newDeleteUserRoleCmd(), + newGetUserOrgCmd(), + newUpdateUserRoleCmd(), + }, + } +} + +func newGetUserOrgCmd() simplecobra.Commander { + description := "display org associated with user" + return &support.SimpleCommand{ + NameP: "currentOrg", + Short: description, + Long: description, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + slog.Info("Listing organizations for context", "context", config.Config().GetGDGConfig().GetContext()) + rootCmd.TableObj.AppendHeader(table.Row{"id", "name"}) + org := rootCmd.GrafanaSvc().GetUserOrganization() + if org == nil { + slog.Info("No organizations found") + } else { + rootCmd.TableObj.AppendRow(table.Row{org.ID, org.Name}) + rootCmd.Render(cd.CobraCommand, map[string]interface{}{"id": org.ID, "name": org.Name}) + } + return nil + }, + } +} + +func newListUsers() simplecobra.Commander { + description := "list list an Organization users" + return &support.SimpleCommand{ + NameP: "list", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"listUsers"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + if len(args) < 1 { + return errors.New("requires an orgId to be specified") + } + orgId, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + log.Fatal("unable to parse orgId to numeric value") + } + slog.Info("Listing org users for context", "context", config.Config().GetGDGConfig().GetContext()) + rootCmd.TableObj.AppendHeader(table.Row{"id", "login", "orgId", "name", "email", "role"}) + users := rootCmd.GrafanaSvc().ListOrgUsers(orgId) + if len(users) == 0 { + slog.Info("No users found") + } else { + for _, user := range users { + rootCmd.TableObj.AppendRow(table.Row{user.UserID, user.Login, user.OrgID, user.Name, user.Email, user.Role}) + } + rootCmd.Render(cd.CobraCommand, users) + } + return nil + }, + } +} + +func newUpdateUserRoleCmd() simplecobra.Commander { + description := "updateRole " + return &support.SimpleCommand{ + NameP: "updateRole", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"updateUserRole"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + if len(args) < 3 { + return fmt.Errorf("requires the following parameters to be specified: [ ]\nValid roles are: [%s]", strings.Join(getBasicRoles(), ", ")) + } + orgSlug := args[0] + roleName := args[2] + userId, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + log.Fatal("unable to parse userId to numeric value") + } + slog.Info("Listing org users for context", "context", config.Config().GetGDGConfig().GetContext()) + rootCmd.TableObj.AppendHeader(table.Row{"login", "orgId", "name", "email", "role"}) + err = rootCmd.GrafanaSvc().UpdateUserInOrg(roleName, orgSlug, userId) + if err != nil { + slog.Error("Unable to update Org user") + } else { + slog.Info("User has been updated") + } + return nil + }, + } +} + +func newAddUserRoleCmd() simplecobra.Commander { + description := "add " + return &support.SimpleCommand{ + NameP: "add", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"addUser", "addUsers"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + if len(args) < 3 { + return fmt.Errorf("requires the following parameters to be specified: [ ]\nValid roles are: [%s]", strings.Join(getBasicRoles(), ", ")) + } + orgSlug := args[0] + userId, err := strconv.ParseInt(args[1], 10, 64) + role := args[2] + if err != nil { + log.Fatal("unable to parse userId to numeric value") + } + slog.Info("Add user to org for context", + slog.Any("context", config.Config().GetGDGConfig().GetContext()), + slog.Any("organization", config.Config().GetDefaultGrafanaConfig().OrganizationName), + ) + if !validBasicRole(role) { + log.Fatalf("Invalid role specified, '%s'. Valid roles are:[%s]", role, strings.Join(getBasicRoles(), ", ")) + } + rootCmd.TableObj.AppendHeader(table.Row{"login", "orgId", "name", "email", "role"}) + err = rootCmd.GrafanaSvc().AddUserToOrg(role, orgSlug, userId) + if err != nil { + slog.Error("Unable to add user to Org", slog.Any("err", err.Error())) + } else { + slog.Info("User has been add to Org", slog.Any("userId", userId), slog.String("organization", orgSlug)) + } + return nil + }, + } +} + +func newDeleteUserRoleCmd() simplecobra.Commander { + description := "deleteUser removes a user from the given Organization (This will NOT delete the actual user from Grafana)" + return &support.SimpleCommand{ + NameP: "delete", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"deleteUser", "remove"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + if len(args) < 2 { + return fmt.Errorf("requires the following parameters to be specified: [ ]") + } + orgSlug := args[0] + userId, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + log.Fatal("unable to parse userId to numeric value") + } + slog.Info("Update org for context", "context", config.Config().GetGDGConfig().GetContext()) + err = rootCmd.GrafanaSvc().DeleteUserFromOrg(orgSlug, userId) + if err != nil { + slog.Error("Unable to remove user from Org", slog.Any("err", err.Error())) + } else { + slog.Info("User has been removed from Org", "userId", orgSlug) + } + return nil + }, + } +} diff --git a/cli/tools/organizations.go b/cli/tools/organizations.go index 7e291109..9a68f51e 100644 --- a/cli/tools/organizations.go +++ b/cli/tools/organizations.go @@ -3,10 +3,8 @@ package tools import ( "context" "errors" - "fmt" "log" "log/slog" - "strconv" "github.com/bep/simplecobra" "github.com/esnet/gdg/cli/support" @@ -28,13 +26,9 @@ func newOrgCommand() simplecobra.Commander { }, CommandsList: []simplecobra.Commander{ newSetOrgCmd(), - newGetUserOrgCmd(), newGetTokenOrgCmd(), // Users - newListUsers(), - newUpdateUserRoleCmd(), - newAddUserRoleCmd(), - newDeleteUserRoleCmd(), + newOrgUsersCommand(), // Preferences newOrgPreferenceCommand(), }, @@ -77,27 +71,6 @@ func newSetOrgCmd() simplecobra.Commander { } } -func newGetUserOrgCmd() simplecobra.Commander { - description := "display org associated with user" - return &support.SimpleCommand{ - NameP: "userOrg", - Short: description, - Long: description, - RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { - slog.Info("Listing organizations for context", "context", config.Config().GetGDGConfig().GetContext()) - rootCmd.TableObj.AppendHeader(table.Row{"id", "name"}) - org := rootCmd.GrafanaSvc().GetUserOrganization() - if org == nil { - slog.Info("No organizations found") - } else { - rootCmd.TableObj.AppendRow(table.Row{org.ID, org.Name}) - rootCmd.Render(cd.CobraCommand, map[string]interface{}{"id": org.ID, "name": org.Name}) - } - return nil - }, - } -} - func newGetTokenOrgCmd() simplecobra.Commander { description := "display org associated with token" return &support.SimpleCommand{ @@ -118,117 +91,3 @@ func newGetTokenOrgCmd() simplecobra.Commander { }, } } - -func newListUsers() simplecobra.Commander { - description := "listUsers list an Organization users" - return &support.SimpleCommand{ - NameP: "listUsers", - Short: description, - Long: description, - RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { - if len(args) < 1 { - return errors.New("requires an orgId to be specified") - } - orgId, err := strconv.ParseInt(args[0], 10, 64) - if err != nil { - log.Fatal("unable to parse orgId to numeric value") - } - slog.Info("Listing org users for context", "context", config.Config().GetGDGConfig().GetContext()) - rootCmd.TableObj.AppendHeader(table.Row{"id", "login", "orgId", "name", "email", "role"}) - users := rootCmd.GrafanaSvc().ListOrgUsers(orgId) - if len(users) == 0 { - slog.Info("No users found") - } else { - for _, user := range users { - rootCmd.TableObj.AppendRow(table.Row{user.UserID, user.Login, user.OrgID, user.Name, user.Email, user.Role}) - } - rootCmd.Render(cd.CobraCommand, users) - } - return nil - }, - } -} - -func newUpdateUserRoleCmd() simplecobra.Commander { - description := "updateUserRole " - return &support.SimpleCommand{ - NameP: "updateUserRole", - Short: description, - Long: description, - RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { - if len(args) < 3 { - return fmt.Errorf("requires the following parameters to be specified: [ ]\nValid roles are: [admin, editor, viewer]") - } - orgSlug := args[0] - roleName := args[2] - userId, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - log.Fatal("unable to parse userId to numeric value") - } - slog.Info("Listing org users for context", "context", config.Config().GetGDGConfig().GetContext()) - rootCmd.TableObj.AppendHeader(table.Row{"login", "orgId", "name", "email", "role"}) - err = rootCmd.GrafanaSvc().UpdateUserInOrg(roleName, orgSlug, userId) - if err != nil { - slog.Error("Unable to update Org user") - } else { - slog.Info("User has been updated") - } - return nil - }, - } -} - -func newAddUserRoleCmd() simplecobra.Commander { - description := "addUser " - return &support.SimpleCommand{ - NameP: "addUser", - Short: description, - Long: description, - RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { - if len(args) < 3 { - return fmt.Errorf("requires the following parameters to be specified: [ ]\nValid roles are: [admin, editor, viewer]") - } - orgSlug := args[0] - userId, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - log.Fatal("unable to parse userId to numeric value") - } - slog.Info("Add user to org for context", "context", config.Config().GetGDGConfig().GetContext()) - rootCmd.TableObj.AppendHeader(table.Row{"login", "orgId", "name", "email", "role"}) - err = rootCmd.GrafanaSvc().AddUserToOrg(args[2], orgSlug, userId) - if err != nil { - slog.Error("Unable to add user to Org") - } else { - slog.Info("User has been add to Org", slog.Any("userId", userId), slog.String("organization", orgSlug)) - } - return nil - }, - } -} - -func newDeleteUserRoleCmd() simplecobra.Commander { - description := "deleteUser removes a user from the given Organization (This will NOT delete the actual user from Grafana)" - return &support.SimpleCommand{ - NameP: "deleteUser", - Short: description, - Long: description, - RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { - if len(args) < 2 { - return fmt.Errorf("requires the following parameters to be specified: [ ]") - } - orgSlug := args[0] - userId, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - log.Fatal("unable to parse userId to numeric value") - } - slog.Info("Update org for context", "context", config.Config().GetGDGConfig().GetContext()) - err = rootCmd.GrafanaSvc().DeleteUserFromOrg(orgSlug, userId) - if err != nil { - slog.Error("Unable to remove user from Org") - } else { - slog.Info("User has been removed from Org", "userId", args[0]) - } - return nil - }, - } -} diff --git a/cli/tools/tools.go b/cli/tools/tools.go index c4c9f1cb..8fb94a26 100644 --- a/cli/tools/tools.go +++ b/cli/tools/tools.go @@ -2,12 +2,21 @@ package tools import ( "context" + "slices" "github.com/bep/simplecobra" "github.com/esnet/gdg/cli/support" "github.com/spf13/cobra" ) +func getBasicRoles() []string { + return []string{"admin", "editor", "viewer"} +} + +func validBasicRole(role string) bool { + return slices.Contains(getBasicRoles(), role) +} + func NewToolsCommand() simplecobra.Commander { description := "A collection of tools to manage a grafana instance" return &support.SimpleCommand{ diff --git a/website/content/docs/usage_guide/tools_guide.md b/website/content/docs/usage_guide/tools_guide.md index c1350c25..a394f6dc 100644 --- a/website/content/docs/usage_guide/tools_guide.md +++ b/website/content/docs/usage_guide/tools_guide.md @@ -157,6 +157,19 @@ gdg t orgs prefs get --orgName "Main Org." └──────────────────┴─────────┘ ``` +### Organization Users CRUD + +```sh +gdg tools organizations users add [OrgSlug] [userID] Role[admin,editor,viewer] ## Add user to org + example: gdg tools organizations users add testing 3 admin +gdg tools organizations users list OrgID ## List all users for a given org + example: gdg tools organizations users list 4 +gdg tools organizations users updateRole [OrgSlug] [UserId] Role[admin,editor,viewer] + example: gdg tools organizations users updateRole testing 2 admin +gdg tools organizations users currentOrg ## displays the logged in User's current associated Org +gdg tools organizations users delete OrgID ## Removes a user from the given org +``` + ### Users CRUD is under the 'backup' command. The tools subcommand allows you to promote a given user to a grafana admin if you have the permission to do so.