Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit a7a7b37

Browse files
author
Faris Huskovic
authored
feature: support filtering by workspace provider when listing environ… (#282)
* feature: support filtering by workspace provider when listing environments and resources
1 parent 9ff25a2 commit a7a7b37

File tree

9 files changed

+140
-4
lines changed

9 files changed

+140
-4
lines changed

coder

12.6 MB
Binary file not shown.

coder-sdk/env.go

+9
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,12 @@ func (c *DefaultClient) EnvironmentByID(ctx context.Context, id string) (*Enviro
339339
}
340340
return &env, nil
341341
}
342+
343+
// EnvironmentsByWorkspaceProvider returns all environments that belong to a particular workspace provider.
344+
func (c *DefaultClient) EnvironmentsByWorkspaceProvider(ctx context.Context, wpID string) ([]Environment, error) {
345+
var envs []Environment
346+
if err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools/"+wpID+"/environments", nil, &envs); err != nil {
347+
return nil, err
348+
}
349+
return envs, nil
350+
}

coder-sdk/interface.go

+3
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ type Client interface {
130130
// EnvironmentByID get the details of an environment by its id.
131131
EnvironmentByID(ctx context.Context, id string) (*Environment, error)
132132

133+
// EnvironmentsByWorkspaceProvider returns environments that belong to a particular workspace provider.
134+
EnvironmentsByWorkspaceProvider(ctx context.Context, wpID string) ([]Environment, error)
135+
133136
// ImportImage creates a new image and optionally a new registry.
134137
ImportImage(ctx context.Context, req ImportImageReq) (*Image, error)
135138

docs/coder_envs_ls.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ coder envs ls [flags]
1313
### Options
1414

1515
```
16-
-h, --help help for ls
17-
-o, --output string human | json (default "human")
18-
--user string Specify the user whose resources to target (default "me")
16+
-h, --help help for ls
17+
-o, --output string human | json (default "human")
18+
-p, --provider string Filter environments by a particular workspace provider name.
19+
--user string Specify the user whose resources to target (default "me")
1920
```
2021

2122
### Options inherited from parent commands

internal/cmd/ceapi.go

+34
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"golang.org/x/xerrors"
99

1010
"cdr.dev/coder-cli/coder-sdk"
11+
"cdr.dev/coder-cli/internal/coderutil"
1112
"cdr.dev/coder-cli/pkg/clog"
1213
)
1314

@@ -202,3 +203,36 @@ func getUserOrgs(ctx context.Context, client coder.Client, email string) ([]code
202203
}
203204
return lookupUserOrgs(u, orgs), nil
204205
}
206+
207+
func getEnvsByProvider(ctx context.Context, client coder.Client, wpName, userEmail string) ([]coder.Environment, error) {
208+
wp, err := coderutil.ProviderByName(ctx, client, wpName)
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
envs, err := client.EnvironmentsByWorkspaceProvider(ctx, wp.ID)
214+
if err != nil {
215+
return nil, err
216+
}
217+
218+
envs, err = filterEnvsByUser(ctx, client, userEmail, envs)
219+
if err != nil {
220+
return nil, err
221+
}
222+
return envs, nil
223+
}
224+
225+
func filterEnvsByUser(ctx context.Context, client coder.Client, userEmail string, envs []coder.Environment) ([]coder.Environment, error) {
226+
user, err := client.UserByEmail(ctx, userEmail)
227+
if err != nil {
228+
return nil, xerrors.Errorf("get user: %w", err)
229+
}
230+
231+
var filteredEnvs []coder.Environment
232+
for _, env := range envs {
233+
if env.UserID == user.ID {
234+
filteredEnvs = append(filteredEnvs, env)
235+
}
236+
}
237+
return filteredEnvs, nil
238+
}

internal/cmd/cli_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ func (r result) clogError(t *testing.T) clog.CLIError {
133133
return cliErr
134134
}
135135

136+
//nolint
136137
func execute(t *testing.T, in io.Reader, args ...string) result {
137138
cmd := Make()
138139

internal/cmd/envs.go

+8
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func lsEnvsCommand() *cobra.Command {
5252
var (
5353
outputFmt string
5454
user string
55+
provider string
5556
)
5657

5758
cmd := &cobra.Command{
@@ -68,6 +69,12 @@ func lsEnvsCommand() *cobra.Command {
6869
if err != nil {
6970
return err
7071
}
72+
if provider != "" {
73+
envs, err = getEnvsByProvider(ctx, client, provider, user)
74+
if err != nil {
75+
return err
76+
}
77+
}
7178
if len(envs) < 1 {
7279
clog.LogInfo("no environments found")
7380
envs = []coder.Environment{} // ensures that json output still marshals
@@ -95,6 +102,7 @@ func lsEnvsCommand() *cobra.Command {
95102

96103
cmd.Flags().StringVar(&user, "user", coder.Me, "Specify the user whose resources to target")
97104
cmd.Flags().StringVarP(&outputFmt, "output", "o", humanOutput, "human | json")
105+
cmd.Flags().StringVarP(&provider, "provider", "p", "", "Filter environments by a particular workspace provider name.")
98106

99107
return cmd
100108
}

internal/cmd/envs_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,30 @@ func Test_envs_ls(t *testing.T) {
1717
var envs []coder.Environment
1818
res.stdoutUnmarshals(t, &envs)
1919
}
20+
21+
//nolint
22+
func Test_envs_ls_by_provider(t *testing.T) {
23+
for _, test := range []struct {
24+
name string
25+
command []string
26+
assert func(r result)
27+
}{
28+
{
29+
name: "simple list",
30+
command: []string{"envs", "ls", "--provider", "built-in"},
31+
assert: func(r result) { r.success(t) },
32+
},
33+
{
34+
name: "list as json",
35+
command: []string{"envs", "ls", "--provider", "built-in", "--output", "json"},
36+
assert: func(r result) {
37+
var envs []coder.Environment
38+
r.stdoutUnmarshals(t, &envs)
39+
},
40+
},
41+
} {
42+
t.Run(test.name, func(t *testing.T) {
43+
test.assert(execute(t, nil, test.command...))
44+
})
45+
}
46+
}

internal/cmd/resourcemanager.go

+54-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type resourceTopOptions struct {
2828
user string
2929
org string
3030
sortBy string
31+
provider string
3132
showEmptyGroups bool
3233
}
3334

@@ -41,11 +42,13 @@ func resourceTop() *cobra.Command {
4142
Example: `coder resources top --group org
4243
coder resources top --group org --verbose --org DevOps
4344
coder resources top --group user --verbose --user name@example.com
45+
coder resources top --group provider --verbose --provider myprovider
4446
coder resources top --sort-by memory --show-empty`,
4547
}
46-
cmd.Flags().StringVar(&options.group, "group", "user", "the grouping parameter (user|org)")
48+
cmd.Flags().StringVar(&options.group, "group", "user", "the grouping parameter (user|org|provider)")
4749
cmd.Flags().StringVar(&options.user, "user", "", "filter by a user email")
4850
cmd.Flags().StringVar(&options.org, "org", "", "filter by the name of an organization")
51+
cmd.Flags().StringVar(&options.provider, "provider", "", "filter by the name of a workspace provider")
4952
cmd.Flags().StringVar(&options.sortBy, "sort-by", "cpu", "field to sort aggregate groups and environments by (cpu|memory)")
5053
cmd.Flags().BoolVar(&options.showEmptyGroups, "show-empty", false, "show groups with zero active environments")
5154

@@ -84,13 +87,20 @@ func runResourceTop(options *resourceTopOptions) func(cmd *cobra.Command, args [
8487
return xerrors.Errorf("get organizations: %w", err)
8588
}
8689

90+
providers, err := client.WorkspaceProviders(ctx)
91+
if err != nil {
92+
return xerrors.Errorf("get workspace providers: %w", err)
93+
}
94+
8795
var groups []groupable
8896
var labeler envLabeler
8997
switch options.group {
9098
case "user":
9199
groups, labeler = aggregateByUser(users, orgs, envs, *options)
92100
case "org":
93101
groups, labeler = aggregateByOrg(users, orgs, envs, *options)
102+
case "provider":
103+
groups, labeler = aggregateByProvider(providers.Kubernetes, orgs, envs, *options)
94104
default:
95105
return xerrors.Errorf("unknown --group %q", options.group)
96106
}
@@ -143,6 +153,28 @@ func aggregateByOrg(users []coder.User, orgs []coder.Organization, envs []coder.
143153
return groups, userLabeler{userIDMap}
144154
}
145155

156+
func aggregateByProvider(providers []coder.KubernetesProvider, orgs []coder.Organization, envs []coder.Environment, options resourceTopOptions) ([]groupable, envLabeler) {
157+
var groups []groupable
158+
providerIDMap := make(map[string]coder.KubernetesProvider)
159+
for _, p := range providers {
160+
providerIDMap[p.ID] = p
161+
}
162+
providerEnvs := make(map[string][]coder.Environment, len(orgs))
163+
for _, e := range envs {
164+
if options.provider != "" && providerIDMap[e.ResourcePoolID].Name != options.provider {
165+
continue
166+
}
167+
providerEnvs[e.ResourcePoolID] = append(providerEnvs[e.ResourcePoolID], e)
168+
}
169+
for _, p := range providers {
170+
if options.provider != "" && p.Name != options.provider {
171+
continue
172+
}
173+
groups = append(groups, providerGrouping{provider: p, envs: providerEnvs[p.ID]})
174+
}
175+
return groups, providerLabeler{providerIDMap}
176+
}
177+
146178
// groupable specifies a structure capable of being an aggregation group of environments (user, org, all).
147179
type groupable interface {
148180
header() string
@@ -179,6 +211,19 @@ func (o orgGrouping) header() string {
179211
return fmt.Sprintf("%s\t(%v member%s)", truncate(o.org.Name, 20, "..."), len(o.org.Members), plural)
180212
}
181213

214+
type providerGrouping struct {
215+
provider coder.KubernetesProvider
216+
envs []coder.Environment
217+
}
218+
219+
func (p providerGrouping) environments() []coder.Environment {
220+
return p.envs
221+
}
222+
223+
func (p providerGrouping) header() string {
224+
return fmt.Sprintf("%s\t", truncate(p.provider.Name, 20, "..."))
225+
}
226+
182227
func printResourceTop(writer io.Writer, groups []groupable, labeler envLabeler, showEmptyGroups bool, sortBy string) error {
183228
tabwriter := tabwriter.NewWriter(writer, 0, 0, 4, ' ', 0)
184229
defer func() { _ = tabwriter.Flush() }()
@@ -287,6 +332,14 @@ func (u userLabeler) label(e coder.Environment) string {
287332
return fmt.Sprintf("[user: %s]", u.userMap[e.UserID].Email)
288333
}
289334

335+
type providerLabeler struct {
336+
providerMap map[string]coder.KubernetesProvider
337+
}
338+
339+
func (p providerLabeler) label(e coder.Environment) string {
340+
return fmt.Sprintf("[provider: %s]", p.providerMap[e.ResourcePoolID].Name)
341+
}
342+
290343
func aggregateEnvResources(envs []coder.Environment) resources {
291344
var aggregate resources
292345
for _, e := range envs {

0 commit comments

Comments
 (0)