Skip to content

Commit

Permalink
Add listing of organization members (#378)
Browse files Browse the repository at this point in the history
* Add 'members' subcommand to 'orgs' to list organization members

* Add support for pagination

* Refactor pagination code for resuse

* Switch from `orgs members` to `orgs members list`

* Fix formatting of keys

Co-authored-by: Rita Zerrizuela <[email protected]>
  • Loading branch information
sergeybykov and Widcket authored Jan 18, 2022
1 parent bc63c3a commit 1a646d4
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 36 deletions.
2 changes: 1 addition & 1 deletion internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var requiredScopes = []string{
"read:anomaly_blocks", "delete:anomaly_blocks",
"create:log_streams", "delete:log_streams", "read:log_streams", "update:log_streams",
"create:actions", "delete:actions", "read:actions", "update:actions",
"create:organizations", "delete:organizations", "read:organizations", "update:organizations",
"create:organizations", "delete:organizations", "read:organizations", "update:organizations", "read:organization_members",
"read:prompts", "update:prompts",
}

Expand Down
5 changes: 5 additions & 0 deletions internal/auth0/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ type OrganizationAPI interface {
//
// See: https://auth0.com/docs/api/management/v2/#!/Organizations/get_organizations
List(opts ...management.RequestOption) (c *management.OrganizationList, err error)

// List members of an organization
//
// See: https://auth0.com/docs/api/management/v2#!/Organizations/get_members
Members(id string, opts ...management.RequestOption) (o *management.OrganizationMemberList, err error)
}
49 changes: 18 additions & 31 deletions internal/cli/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,41 +208,28 @@ auth0 apps create`,
auth0 apps ls
auth0 apps ls -n 100`,
RunE: func(cmd *cobra.Command, args []string) error {
var list []*management.Client
if err := ansi.Waiting(func() error {
pageSize := defaultPageSize
page := 0
for {
if inputs.Number > 0 {
// determine page size to avoid getting unwanted elements
want := inputs.Number - int(len(list))
if want == 0 {
return nil
}
if want < defaultPageSize {
pageSize = want
} else {
pageSize = defaultPageSize
}
}
res, err := cli.api.Client.List(
management.Context(cmd.Context()),
management.PerPage(pageSize),
management.Page(page))
if err != nil {
return err
list, err := getWithPagination(
cmd.Context(),
inputs.Number,
func(opts ...management.RequestOption) (result []interface{}, hasNext bool, err error) {
res, apiErr := cli.api.Client.List(opts...)
if apiErr != nil {
return nil, false, apiErr
}
page++
list = append(list, res.Clients...)
if len(list) == inputs.Number || !res.List.HasNext() {
return nil
var output []interface{}
for _, client := range res.Clients {
output = append(output, client)
}
}
}); err != nil {
return output, res.HasNext(), nil
})
if err != nil {
return fmt.Errorf("An unexpected error occurred: %w", err)
}

cli.renderer.ApplicationList(list, inputs.Reveal)
var typedList []*management.Client
for _, item := range list {
typedList = append(typedList, item.(*management.Client))
}
cli.renderer.ApplicationList(typedList, inputs.Reveal)
return nil
},
}
Expand Down
120 changes: 116 additions & 4 deletions internal/cli/organizations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"context"
"errors"
"fmt"
"net/url"
Expand Down Expand Up @@ -82,6 +83,7 @@ func organizationsCmd(cli *cli) *cobra.Command {
cmd.AddCommand(updateOrganizationCmd(cli))
cmd.AddCommand(deleteOrganizationCmd(cli))
cmd.AddCommand(openOrganizationCmd(cli))
cmd.AddCommand(membersOrganizationCmd(cli))

return cmd
}
Expand Down Expand Up @@ -160,7 +162,7 @@ func createOrganizationCmd(cli *cli) *cobra.Command {
Name string
DisplayName string
LogoURL string
AccentColor string
AccentColor string
BackgroundColor string
Metadata map[string]string
}
Expand Down Expand Up @@ -208,7 +210,7 @@ auth0 orgs create --n myorganization -d "My Organization" -m "KEY=value" -m "OTH
if isAccentColorSet {
o.Branding.Colors[apiOrganizationColorPrimary] = inputs.AccentColor
}

if isBackgroundColorSet {
o.Branding.Colors[apiOrganizationColorPageBackground] = inputs.BackgroundColor
}
Expand Down Expand Up @@ -241,7 +243,7 @@ func updateOrganizationCmd(cli *cli) *cobra.Command {
ID string
DisplayName string
LogoURL string
AccentColor string
AccentColor string
BackgroundColor string
Metadata map[string]string
}
Expand Down Expand Up @@ -306,7 +308,7 @@ auth0 orgs update <id> -d "My Organization" -m "KEY=value" -m "OTHER_KEY=other_v
} else if currentHasBranding {
o.Branding.LogoUrl = current.Branding.LogoUrl
}

if needToAddColors {
o.Branding.Colors = map[string]string{}

Expand Down Expand Up @@ -422,6 +424,73 @@ func openOrganizationCmd(cli *cli) *cobra.Command {
return cmd
}

func membersOrganizationCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "members",
Short: "Manage members of an organization",
Long: "Manage members of an organization.",
}

cmd.SetUsageTemplate(resourceUsageTemplate())
cmd.AddCommand(listMembersOrganizationCmd(cli))

return cmd
}

func listMembersOrganizationCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
Number int
}

cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Args: cobra.MaximumNArgs(1),
Short: "List members of an organization",
Long: "List members of an organization.",
Example: `auth0 orgs members list
auth0 orgs members ls <id>`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
err := organizationID.Pick(cmd, &inputs.ID, cli.organizationPickerOptions)
if err != nil {
return err
}
} else {
inputs.ID = args[0]
}

list, err := getWithPagination(
cmd.Context(),
inputs.Number,
func(opts ...management.RequestOption) (result []interface{}, hasNext bool, apiErr error) {
members, apiErr := cli.api.Organization.Members(url.PathEscape(inputs.ID), opts...)
if apiErr != nil {
return nil, false, apiErr
}
var output []interface{}
for _, member := range members.Members {
output = append(output, member)
}
return output, members.HasNext(), nil
})

if err != nil {
return fmt.Errorf("Unable to list members of an organization with Id '%s': %w", inputs.ID, err)
}
var typedList []management.OrganizationMember
for _, item := range list {
typedList = append(typedList, item.(management.OrganizationMember))
}
cli.renderer.MembersList(typedList)
return nil
},
}

return cmd
}

func (c *cli) organizationPickerOptions() (pickerOptions, error) {
list, err := c.api.Organization.List()
if err != nil {
Expand Down Expand Up @@ -458,3 +527,46 @@ func apiOrganizationMetadataFor(metadata map[string]string) map[string]interface
}
return res
}

func getWithPagination(
context context.Context,
limit int,
api func(opts ...management.RequestOption) (result []interface{}, hasNext bool, err error),
) ([]interface{}, error) {

var list []interface{}
if err := ansi.Waiting(func() error {
pageSize := defaultPageSize
page := 0
for {
if limit > 0 {
// determine page size to avoid getting unwanted elements
want := limit - int(len(list))
if want == 0 {
return nil
}
if want < defaultPageSize {
pageSize = want
} else {
pageSize = defaultPageSize
}
}
res, hasNext, err := api(
management.Context(context),
management.PerPage(pageSize),
management.Page(page))
if err != nil {
return err
}
page++
list = append(list, res...)
if len(list) == limit || !hasNext {
return nil
}
}

}); err != nil {
return nil, err
}
return list, nil
}
61 changes: 61 additions & 0 deletions internal/display/members.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package display

import (
"io"

"github.com/auth0/auth0-cli/internal/ansi"
"gopkg.in/auth0.v5/management"
)

type membersView struct {
ID string
Name string
Email string
PictureURL string
raw interface{}
}

func (v *membersView) AsTableHeader() []string {
return []string{"ID", "Name", "Email", "Picture"}
}

func (v *membersView) AsTableRow() []string {
return []string{ansi.Faint(v.ID), v.Name, v.Email, v.PictureURL}
}

func (v *membersView) KeyValues() [][]string {
return [][]string{
{"ID", ansi.Faint(v.ID)},
{"NAME", v.Name},
{"EMAIL", v.Email},
{"PICTURE URL", v.PictureURL},
}
}

func (v *membersView) Object() interface{} {
return v.raw
}

func (r *Renderer) MembersList(members []management.OrganizationMember) {
resource := "members"

r.Heading(resource)

var res []View
for _, m := range members {
res = append(res, makeMembersView(&m, r.MessageWriter))
}

r.Results(res)
}

func makeMembersView(member *management.OrganizationMember, w io.Writer) *membersView {

return &membersView{
ID: member.GetUserID(),
Name: member.GetName(),
Email: member.GetEmail(),
PictureURL: member.GetPicture(),
raw: member,
}
}

0 comments on commit 1a646d4

Please sign in to comment.