Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,17 @@ $ dbxcli share-link update --output=json https://www.dropbox.com/s/example/old.p
$ dbxcli share-link revoke --output=json https://www.dropbox.com/s/example/old.pdf
$ dbxcli share-link download --output=json https://www.dropbox.com/s/example/old.pdf ./old.pdf
$ dbxcli share list folder --output=json
$ dbxcli team info --output=json
$ dbxcli team list-members --output=json
$ dbxcli team list-groups --output=json
$ dbxcli team add-member --output=json user@example.com User Name
$ dbxcli team remove-member --output=json user@example.com
$ dbxcli mkdir --output=json /new-folder
$ dbxcli rm --output=json /old-file.txt
$ dbxcli restore --output=json /Reports/old.pdf 015f...
```

Structured success output is rolling out command by command. Currently migrated commands are `version`, `account`, `du`, `ls`, `search`, `revs`, `cp`, `mv`, `put`, `get`, `share-link create`, `share-link list`, `share-link info`, `share-link update`, `share-link revoke`, `share-link download`, `share list folder`, `mkdir`, `rm`, and `restore`. Commands that have not been migrated return a JSON error whose `error.message` is `structured output is not supported for this command yet` when used with `--output=json`.
Structured success output is rolling out command by command. Currently migrated commands are `version`, `account`, `du`, `ls`, `search`, `revs`, `cp`, `mv`, `put`, `get`, `share-link create`, `share-link list`, `share-link info`, `share-link update`, `share-link revoke`, `share-link download`, `share list folder`, `team info`, `team list-members`, `team list-groups`, `team add-member`, `team remove-member`, `mkdir`, `rm`, and `restore`. Commands that have not been migrated return a JSON error whose `error.message` is `structured output is not supported for this command yet` when used with `--output=json`.

Command results and JSON errors are written to stdout. Status, progress, human-facing warnings, diagnostics, and verbose logs are written to stderr. JSON errors include a `warnings` array for machine-actionable warnings; it is `[]` when no warnings are present. Successful JSON payloads use the same `warnings` field.

Expand Down Expand Up @@ -422,6 +427,29 @@ The legacy `share list folder` command also supports operation-style JSON. It us
}
```

Team commands use the same operation-style wrapper. `team info` returns a single `team` result, `team list-members` and `team list-groups` return `listed` results, and mutating member commands return the Dropbox launch status:

```json
{
"input": {},
"results": [
{
"status": "listed",
"kind": "team_member",
"result": {
"type": "team_member",
"team_member_id": "dbmid:...",
"email": "user@example.com",
"email_verified": true,
"status": "active",
"role": "member_only"
}
}
],
"warnings": []
}
```

`get --output=json <source> -` and `share-link download --output=json <url> -` are not supported because stdout is reserved for downloaded file bytes when the target is `-`.

In JSON mode, command errors are written to stdout as JSON, including errors from commands that do not yet support structured success output. The process still exits with a non-zero status. Detailed diagnostics may also be written to stderr:
Expand Down
22 changes: 18 additions & 4 deletions cmd/add-member.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cmd
import (
"errors"
"fmt"
"io"

"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team"
"github.com/spf13/cobra"
Expand All @@ -26,7 +27,7 @@ func addMember(cmd *cobra.Command, args []string) (err error) {
if len(args) != 3 {
return errors.New("`add-member` requires `email`, `first`, and `last` arguments")
}
dbx := team.New(config)
dbx := teamNewFunc(config)

email := args[0]
firstName := args[1]
Expand All @@ -39,10 +40,22 @@ func addMember(cmd *cobra.Command, args []string) (err error) {
if err != nil {
return err
}
if res.Tag == "complete" {
fmt.Printf("User successfully added to the team.\n")
input := teamMemberAddInput{
Email: email,
FirstName: firstName,
LastName: lastName,
}
return
return commandOutput(cmd).Render(func(w io.Writer) error {
return renderTeamMemberAdd(w, res)
}, teamMemberAddOperationOutput(input, res))
}

func renderTeamMemberAdd(out io.Writer, res *team.MembersAddLaunch) error {
if res != nil && res.Tag == "complete" {
_, err := fmt.Fprintln(out, "User successfully added to the team.")
return err
}
return nil
}

// addMemberCmd represents the add-member command
Expand All @@ -54,4 +67,5 @@ var addMemberCmd = &cobra.Command{

func init() {
teamCmd.AddCommand(addMemberCmd)
enableStructuredOutput(addMemberCmd)
}
13 changes: 10 additions & 3 deletions cmd/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,28 @@ package cmd

import (
"fmt"
"os"
"io"
"text/tabwriter"

"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team"
"github.com/spf13/cobra"
)

func info(cmd *cobra.Command, args []string) (err error) {
dbx := team.New(config)
dbx := teamNewFunc(config)
res, err := dbx.GetInfo()
if err != nil {
return err
}

return commandOutput(cmd).Render(func(w io.Writer) error {
return renderTeamInfo(w, res)
}, teamInfoOperationOutput(res))
}

func renderTeamInfo(out io.Writer, res *team.TeamGetInfoResult) error {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 4, 8, 1, ' ', 0)
w.Init(out, 4, 8, 1, ' ', 0)
fmt.Fprintf(w, "Name:\t%s\n", res.Name)
fmt.Fprintf(w, "Team Id:\t%s\n", res.TeamId)
fmt.Fprintf(w, "Licensed Users:\t%d\n", res.NumLicensedUsers)
Expand All @@ -48,4 +54,5 @@ var infoCmd = &cobra.Command{

func init() {
teamCmd.AddCommand(infoCmd)
enableStructuredOutput(infoCmd)
}
46 changes: 39 additions & 7 deletions cmd/list-groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,61 @@
package cmd

import (
"errors"
"fmt"
"os"
"io"
"text/tabwriter"

"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team_common"
"github.com/spf13/cobra"
)

func listGroups(cmd *cobra.Command, args []string) (err error) {
dbx := team.New(config)
dbx := teamNewFunc(config)
arg := team.NewGroupsListArg()
res, err := dbx.GroupsList(arg)
groups, err := listTeamGroups(dbx, arg)
if err != nil {
return err
}

if len(res.Groups) == 0 {
return
commandVerboseStatus(cmd, "Listed %d team groups", len(groups))

return commandOutput(cmd).Render(func(w io.Writer) error {
return renderTeamGroups(w, groups)
}, newJSONOperationOutput(teamInfoInput{}, teamGroupOperationResults(groups), nil))
}

func listTeamGroups(dbx teamClient, arg *team.GroupsListArg) ([]*team_common.GroupSummary, error) {
var groups []*team_common.GroupSummary
res, err := dbx.GroupsList(arg)
if err != nil {
return nil, err
}
groups = append(groups, res.Groups...)

for res.HasMore {
if res.Cursor == "" {
return nil, errors.New("team group list has more results but no cursor")
}
res, err = dbx.GroupsListContinue(team.NewGroupsListContinueArg(res.Cursor))
if err != nil {
return nil, err
}
groups = append(groups, res.Groups...)
}
return groups, nil
}

func renderTeamGroups(out io.Writer, groups []*team_common.GroupSummary) error {
if len(groups) == 0 {
return nil
}

w := new(tabwriter.Writer)
w.Init(os.Stdout, 4, 8, 1, ' ', 0)
w.Init(out, 4, 8, 1, ' ', 0)
fmt.Fprintf(w, "Name\tId\t# Members\tExternal Id\n")
for _, group := range res.Groups {
for _, group := range groups {
fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", group.GroupName, group.GroupId, group.MemberCount, group.GroupExternalId)
}
return w.Flush()
Expand All @@ -53,4 +84,5 @@ var listGroupsCmd = &cobra.Command{

func init() {
teamCmd.AddCommand(listGroupsCmd)
enableStructuredOutput(listGroupsCmd)
}
45 changes: 38 additions & 7 deletions cmd/list-members.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,61 @@
package cmd

import (
"errors"
"fmt"
"os"
"io"
"text/tabwriter"

"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team"
"github.com/spf13/cobra"
)

func listMembers(cmd *cobra.Command, args []string) (err error) {
dbx := team.New(config)
dbx := teamNewFunc(config)
arg := team.NewMembersListArg()
res, err := dbx.MembersList(arg)
members, err := listTeamMembers(dbx, arg)
if err != nil {
return err
}

if len(res.Members) == 0 {
return
commandVerboseStatus(cmd, "Listed %d team members", len(members))

return commandOutput(cmd).Render(func(w io.Writer) error {
return renderTeamMembers(w, members)
}, newJSONOperationOutput(teamInfoInput{}, teamMemberOperationResults(members), nil))
}

func listTeamMembers(dbx teamClient, arg *team.MembersListArg) ([]*team.TeamMemberInfo, error) {
var members []*team.TeamMemberInfo
res, err := dbx.MembersList(arg)
if err != nil {
return nil, err
}
members = append(members, res.Members...)

for res.HasMore {
if res.Cursor == "" {
return nil, errors.New("team member list has more results but no cursor")
}
res, err = dbx.MembersListContinue(team.NewMembersListContinueArg(res.Cursor))
if err != nil {
return nil, err
}
members = append(members, res.Members...)
}
return members, nil
}

func renderTeamMembers(out io.Writer, members []*team.TeamMemberInfo) error {
if len(members) == 0 {
return nil
}

w := new(tabwriter.Writer)
w.Init(os.Stdout, 4, 8, 1, ' ', 0)
w.Init(out, 4, 8, 1, ' ', 0)
fmtStr := "%s\t%s\t%s\t%s\t%s\n"
fmt.Fprintf(w, fmtStr, "Name", "Id", "Status", "Email", "Role")
for _, member := range res.Members {
for _, member := range members {
fmt.Fprintf(w, fmtStr,
member.Profile.Name.DisplayName,
member.Profile.TeamMemberId,
Expand All @@ -59,4 +89,5 @@ var listMembersCmd = &cobra.Command{

func init() {
teamCmd.AddCommand(listMembersCmd)
enableStructuredOutput(listMembersCmd)
}
2 changes: 1 addition & 1 deletion cmd/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func jsonErrorCode(err error) string {
return "unknown_flag"
case strings.Contains(message, "path exists and is not a folder"):
return "path_conflict"
case strings.Contains(message, "requires a"):
case strings.Contains(message, "requires "):
return "invalid_arguments"
case strings.Contains(message, "accepts an optional"):
return "invalid_arguments"
Expand Down
7 changes: 7 additions & 0 deletions cmd/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,13 @@ func TestJSONErrorCodeOptionalArgumentValidation(t *testing.T) {
}
}

func TestJSONErrorCodeRequiredArgumentValidation(t *testing.T) {
err := errors.New("`add-member` requires `email`, `first`, and `last` arguments")
if got, want := jsonErrorCode(err), "invalid_arguments"; got != want {
t.Fatalf("jsonErrorCode = %q, want %q", got, want)
}
}

func decodeJSONErrorResponse(t *testing.T, value string) jsonErrorResponse {
t.Helper()

Expand Down
19 changes: 15 additions & 4 deletions cmd/remove-member.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ package cmd
import (
"errors"
"fmt"
"io"

"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/async"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team"
"github.com/spf13/cobra"
)
Expand All @@ -27,7 +29,7 @@ func removeMember(cmd *cobra.Command, args []string) (err error) {
return errors.New("`remove-member` requires an `email` argument")
}

dbx := team.New(config)
dbx := teamNewFunc(config)
email := args[0]
selector := &team.UserSelectorArg{Email: email}
selector.Tag = "email"
Expand All @@ -36,10 +38,18 @@ func removeMember(cmd *cobra.Command, args []string) (err error) {
if err != nil {
return err
}
if res.Tag == "complete" {
fmt.Printf("User successfully removed from team.\n")
input := teamMemberRemoveInput{Email: email}
return commandOutput(cmd).Render(func(w io.Writer) error {
return renderTeamMemberRemove(w, res)
}, teamMemberRemoveOperationOutput(input, res))
}

func renderTeamMemberRemove(out io.Writer, res *async.LaunchEmptyResult) error {
if res != nil && res.Tag == "complete" {
_, err := fmt.Fprintln(out, "User successfully removed from team.")
return err
}
return
return nil
}

// removeMemberCmd represents the remove-member command
Expand All @@ -51,4 +61,5 @@ var removeMemberCmd = &cobra.Command{

func init() {
teamCmd.AddCommand(removeMemberCmd)
enableStructuredOutput(removeMemberCmd)
}
Loading
Loading