Skip to content

Commit 4e89fe5

Browse files
authored
feat(account): add commands for listing accounts and permissions (#214)
1 parent 0a0a63d commit 4e89fe5

9 files changed

Lines changed: 230 additions & 0 deletions

File tree

.github/workflows/examples.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
name: Examples
2+
23
on:
34
pull_request:
45
paths:
56
- "**.go"
67
- "go.mod"
78
- "go.sum"
89
- "examples/**.md"
10+
11+
concurrency:
12+
group: ${{ github.repository }}-${{ github.workflow }}
13+
cancel-in-progress: false
14+
915
jobs:
1016
test:
1117
name: Test

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Support for storage encryption to storage `create`, `clone`, `show`, and `list` commands as well as server `create` and `show` commands.
1313
- _Managed object storages_ field to human readable output of `account show`.
14+
- Commands for listing accounts and permissions.
1415

1516
### Removed
1617

internal/commands/account/list.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package account
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
8+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
9+
"github.com/jedib0t/go-pretty/v6/text"
10+
)
11+
12+
// ListCommand creates the 'account list' command
13+
func ListCommand() commands.Command {
14+
return &listCommand{
15+
BaseCommand: commands.New("list", "List sub-accounts", "upctl account list"),
16+
}
17+
}
18+
19+
type listCommand struct {
20+
*commands.BaseCommand
21+
}
22+
23+
// ExecuteWithoutArguments implements commands.NoArgumentCommand
24+
func (l *listCommand) ExecuteWithoutArguments(exec commands.Executor) (output.Output, error) {
25+
svc := exec.Account()
26+
accounts, err := svc.GetAccountList(exec.Context())
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
rows := []output.TableRow{}
32+
for _, a := range accounts {
33+
rows = append(rows, output.TableRow{
34+
a.Username,
35+
a.Type,
36+
a.Roles.Role,
37+
})
38+
}
39+
40+
return output.MarshaledWithHumanOutput{
41+
Value: accounts,
42+
Output: output.Table{
43+
Columns: []output.TableColumn{
44+
{Key: "username", Header: "Username"},
45+
{Key: "type", Header: "Type"},
46+
{Key: "roles", Header: "Roles", Format: formatRoles},
47+
},
48+
Rows: rows,
49+
},
50+
}, nil
51+
}
52+
53+
func formatRoles(val interface{}) (text.Colors, string, error) {
54+
roles, ok := val.([]string)
55+
if !ok {
56+
return nil, "", fmt.Errorf("cannot parse %T, expected []string", val)
57+
}
58+
59+
return nil, strings.Join(roles, ", "), nil
60+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package permissions
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/completion"
8+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/config"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/format"
10+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/namedargs"
11+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
12+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/ui"
13+
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud"
14+
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request"
15+
"github.com/jedib0t/go-pretty/v6/text"
16+
"github.com/spf13/pflag"
17+
)
18+
19+
// ListCommand creates the 'permissions list' command
20+
func ListCommand() commands.Command {
21+
return &listCommand{
22+
BaseCommand: commands.New("list", "List permissions", "upctl account show"),
23+
}
24+
}
25+
26+
type listCommand struct {
27+
*commands.BaseCommand
28+
username string
29+
}
30+
31+
// InitCommand implements Command.InitCommand
32+
func (l *listCommand) InitCommand() {
33+
flagSet := &pflag.FlagSet{}
34+
flagSet.StringVar(&l.username, "username", "", "Filter permissions by username.")
35+
36+
l.AddFlags(flagSet)
37+
}
38+
39+
func (l *listCommand) InitCommandWithConfig(cfg *config.Config) {
40+
_ = l.Cobra().RegisterFlagCompletionFunc("username", namedargs.CompletionFunc(completion.Username{}, cfg))
41+
}
42+
43+
func (l *listCommand) ExecuteWithoutArguments(exec commands.Executor) (output.Output, error) {
44+
svc := exec.All()
45+
permissions, err := svc.GetPermissions(exec.Context(), &request.GetPermissionsRequest{})
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
filtered := make([]upcloud.Permission, 0)
51+
rows := []output.TableRow{}
52+
for _, permission := range permissions {
53+
if permission.User == l.username || l.username == "" {
54+
filtered = append(filtered, permission)
55+
rows = append(rows, output.TableRow{
56+
permission.User,
57+
permission.TargetType,
58+
permission.TargetIdentifier,
59+
permission.Options,
60+
})
61+
}
62+
}
63+
return output.MarshaledWithHumanOutput{
64+
Value: filtered,
65+
Output: output.Table{
66+
Columns: []output.TableColumn{
67+
{Key: "username", Header: "Username"},
68+
{Key: "target_type", Header: "Target type"},
69+
{Key: "target_identifier", Header: "Target identifier", Colour: ui.DefaultUUUIDColours},
70+
{Key: "options", Header: "Options", Format: formatOptions},
71+
},
72+
Rows: rows,
73+
},
74+
}, nil
75+
}
76+
77+
func formatOptions(val interface{}) (text.Colors, string, error) {
78+
options, ok := val.(*upcloud.PermissionOptions)
79+
if !ok {
80+
return nil, "", fmt.Errorf("cannot parse %T, expected *upcloud.PermissionOptions", val)
81+
}
82+
83+
if options == nil {
84+
return nil, "", nil
85+
}
86+
87+
colors, value, _ := format.Boolean(options.Storage)
88+
return nil, fmt.Sprintf("Storage: %s", colors.Sprint(value)), nil
89+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package permissions
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
)
6+
7+
// BasePermissionsCommand creates the base 'permissions' command
8+
func BasePermissionsCommand() commands.Command {
9+
return &permissionsCommand{commands.New("permissions", "Manage permissions")}
10+
}
11+
12+
type permissionsCommand struct {
13+
*commands.BaseCommand
14+
}

internal/commands/all/all.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package all
33
import (
44
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
55
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/account"
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/account/permissions"
67
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database"
78
databaseindex "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/index"
89
databaseproperties "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/properties"
@@ -103,6 +104,11 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) {
103104
// Account
104105
accountCommand := commands.BuildCommand(account.BaseAccountCommand(), rootCmd, conf)
105106
commands.BuildCommand(account.ShowCommand(), accountCommand.Cobra(), conf)
107+
commands.BuildCommand(account.ListCommand(), accountCommand.Cobra(), conf)
108+
109+
// Account permissions
110+
permissionsCommand := commands.BuildCommand(permissions.BasePermissionsCommand(), accountCommand.Cobra(), conf)
111+
commands.BuildCommand(permissions.ListCommand(), permissionsCommand.Cobra(), conf)
106112

107113
// Zone
108114
zoneCommand := commands.BuildCommand(zone.BaseZoneCommand(), rootCmd, conf)

internal/completion/username.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package completion
2+
3+
import (
4+
"context"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/service"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
// Username implements argument completion for zones by id.
12+
type Username struct{}
13+
14+
// make sure Kubernetes implements the interface
15+
var _ Provider = Username{}
16+
17+
// CompleteArgument implements completion.Provider
18+
func (s Username) CompleteArgument(ctx context.Context, svc service.AllServices, toComplete string) ([]string, cobra.ShellCompDirective) {
19+
accounts, err := svc.GetAccountList(ctx)
20+
if err != nil {
21+
return None(toComplete)
22+
}
23+
var vals []string
24+
for _, account := range accounts {
25+
vals = append(vals, account.Username)
26+
}
27+
28+
return MatchStringPrefix(vals, toComplete, true), cobra.ShellCompDirectiveNoFileComp
29+
}

internal/mock/mock.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,3 +1184,27 @@ func (m *Service) WaitForManagedObjectStorageOperationalState(ctx context.Contex
11841184
func (m *Service) WaitForManagedObjectStorageDeletion(ctx context.Context, r *request.WaitForManagedObjectStorageDeletionRequest) error {
11851185
return nil
11861186
}
1187+
1188+
func (m *Service) GetPermissions(ctx context.Context, r *request.GetPermissionsRequest) (upcloud.Permissions, error) {
1189+
args := m.Called(r)
1190+
if args[0] == nil {
1191+
return nil, args.Error(1)
1192+
}
1193+
return args[0].(upcloud.Permissions), args.Error(1)
1194+
}
1195+
1196+
func (m *Service) GrantPermission(ctx context.Context, r *request.GrantPermissionRequest) (*upcloud.Permission, error) {
1197+
args := m.Called(r)
1198+
if args[0] == nil {
1199+
return nil, args.Error(1)
1200+
}
1201+
return args[0].(*upcloud.Permission), args.Error(1)
1202+
}
1203+
1204+
func (m *Service) RevokePermission(ctx context.Context, r *request.RevokePermissionRequest) error {
1205+
args := m.Called(r)
1206+
if args[0] == nil {
1207+
return args.Error(0)
1208+
}
1209+
return nil
1210+
}

internal/service/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ type AllServices interface {
1818
service.Kubernetes
1919
service.ServerGroup
2020
service.ManagedObjectStorage
21+
service.Permission
2122
}

0 commit comments

Comments
 (0)