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

feature: support filtering by workspace provider when listing environ… #282

Merged
merged 6 commits into from
Mar 11, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
9 changes: 9 additions & 0 deletions coder-sdk/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,12 @@ func (c *DefaultClient) EnvironmentByID(ctx context.Context, id string) (*Enviro
}
return &env, nil
}

// EnvironmentsByWorkspaceProvider returns all environments that belong to a particular workspace provider.
func (c *DefaultClient) EnvironmentsByWorkspaceProvider(ctx context.Context, wpID string) ([]Environment, error) {
var envs []Environment
if err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools/"+wpID+"/environments", nil, &envs); err != nil {
return nil, err
}
return envs, nil
}
3 changes: 3 additions & 0 deletions coder-sdk/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ type Client interface {
// EnvironmentByID get the details of an environment by its id.
EnvironmentByID(ctx context.Context, id string) (*Environment, error)

// EnvironmentsByWorkspaceProvider returns environments that belong to a particular workspace provider.
EnvironmentsByWorkspaceProvider(ctx context.Context, wpID string) ([]Environment, error)

// ImportImage creates a new image and optionally a new registry.
ImportImage(ctx context.Context, req ImportImageReq) (*Image, error)

Expand Down
7 changes: 4 additions & 3 deletions docs/coder_envs_ls.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ coder envs ls [flags]
### Options

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

### Options inherited from parent commands
Expand Down
34 changes: 34 additions & 0 deletions internal/cmd/ceapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"golang.org/x/xerrors"

"cdr.dev/coder-cli/coder-sdk"
"cdr.dev/coder-cli/internal/coderutil"
"cdr.dev/coder-cli/pkg/clog"
)

Expand Down Expand Up @@ -202,3 +203,36 @@ func getUserOrgs(ctx context.Context, client coder.Client, email string) ([]code
}
return lookupUserOrgs(u, orgs), nil
}

func getEnvsByProvider(ctx context.Context, client coder.Client, wpName, userEmail string) ([]coder.Environment, error) {
wp, err := coderutil.ProviderByName(ctx, client, wpName)
if err != nil {
return nil, err
}

envs, err := client.EnvironmentsByWorkspaceProvider(ctx, wp.ID)
if err != nil {
return nil, err
}

envs, err = filterEnvsByUser(ctx, client, userEmail, envs)
if err != nil {
return nil, err
}
return envs, nil
}

func filterEnvsByUser(ctx context.Context, client coder.Client, userEmail string, envs []coder.Environment) ([]coder.Environment, error) {
user, err := client.UserByEmail(ctx, userEmail)
if err != nil {
return nil, xerrors.Errorf("get user: %w", err)
}

var filteredEnvs []coder.Environment
for _, env := range envs {
if env.UserID == user.ID {
filteredEnvs = append(filteredEnvs, env)
}
}
return filteredEnvs, nil
}
1 change: 1 addition & 0 deletions internal/cmd/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func (r result) clogError(t *testing.T) clog.CLIError {
return cliErr
}

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

Expand Down
8 changes: 8 additions & 0 deletions internal/cmd/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func lsEnvsCommand() *cobra.Command {
var (
outputFmt string
user string
provider string
)

cmd := &cobra.Command{
Expand All @@ -67,6 +68,12 @@ func lsEnvsCommand() *cobra.Command {
if err != nil {
return err
}
if provider != "" {
envs, err = getEnvsByProvider(ctx, client, provider, user)
if err != nil {
return err
}
}
if len(envs) < 1 {
clog.LogInfo("no environments found")
envs = []coder.Environment{} // ensures that json output still marshals
Expand Down Expand Up @@ -94,6 +101,7 @@ func lsEnvsCommand() *cobra.Command {

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

return cmd
}
Expand Down
27 changes: 27 additions & 0 deletions internal/cmd/envs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,30 @@ func Test_envs_ls(t *testing.T) {
var envs []coder.Environment
res.stdoutUnmarshals(t, &envs)
}

//nolint
func Test_envs_ls_by_provider(t *testing.T) {
for _, test := range []struct {
name string
command []string
assert func(r result)
}{
{
name: "simple list",
command: []string{"envs", "ls", "--provider", "built-in"},
assert: func(r result) { r.success(t) },
},
{
name: "list as json",
command: []string{"envs", "ls", "--provider", "built-in", "--output", "json"},
assert: func(r result) {
var envs []coder.Environment
r.stdoutUnmarshals(t, &envs)
},
},
} {
t.Run(test.name, func(t *testing.T) {
test.assert(execute(t, nil, test.command...))
})
}
}
42 changes: 41 additions & 1 deletion internal/cmd/resourcemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type resourceTopOptions struct {
user string
org string
sortBy string
provider string
showEmptyGroups bool
}

Expand All @@ -41,11 +42,13 @@ func resourceTop() *cobra.Command {
Example: `coder resources top --group org
coder resources top --group org --verbose --org DevOps
coder resources top --group user --verbose --user [email protected]
coder resources top --group provider --verbose --provider myprovider
coder resources top --sort-by memory --show-empty`,
}
cmd.Flags().StringVar(&options.group, "group", "user", "the grouping parameter (user|org)")
cmd.Flags().StringVar(&options.group, "group", "user", "the grouping parameter (user|org|provider)")
cmd.Flags().StringVar(&options.user, "user", "", "filter by a user email")
cmd.Flags().StringVar(&options.org, "org", "", "filter by the name of an organization")
cmd.Flags().StringVar(&options.provider, "provider", "", "filter by the name of a workspace provider")
cmd.Flags().StringVar(&options.sortBy, "sort-by", "cpu", "field to sort aggregate groups and environments by (cpu|memory)")
cmd.Flags().BoolVar(&options.showEmptyGroups, "show-empty", false, "show groups with zero active environments")

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

providers, err := client.WorkspaceProviders(ctx)
if err != nil {
return xerrors.Errorf("get workspace providers: %w", err)
}

var groups []groupable
var labeler envLabeler
switch options.group {
case "user":
groups, labeler = aggregateByUser(users, orgs, envs, *options)
case "org":
groups, labeler = aggregateByOrg(users, orgs, envs, *options)
case "provider":
groups, labeler = aggregateByProvider(providers.Kubernetes, orgs, envs, *options)
default:
return xerrors.Errorf("unknown --group %q", options.group)
}
Expand Down Expand Up @@ -143,6 +153,28 @@ func aggregateByOrg(users []coder.User, orgs []coder.Organization, envs []coder.
return groups, userLabeler{userIDMap}
}

func aggregateByProvider(providers []coder.KubernetesProvider, orgs []coder.Organization, envs []coder.Environment, options resourceTopOptions) ([]groupable, envLabeler) {
var groups []groupable
providerIDMap := make(map[string]coder.KubernetesProvider)
for _, p := range providers {
providerIDMap[p.ID] = p
}
providerEnvs := make(map[string][]coder.Environment, len(orgs))
for _, e := range envs {
if options.user != "" && providerIDMap[e.ResourcePoolID].Name != options.provider {
continue
}
providerEnvs[e.OrganizationID] = append(providerEnvs[e.OrganizationID], e)
}
for _, o := range orgs {
if options.org != "" && o.Name != options.org {
continue
}
groups = append(groups, orgGrouping{org: o, envs: providerEnvs[o.ID]})
}
return groups, providerLabeler{providerIDMap}
}

// groupable specifies a structure capable of being an aggregation group of environments (user, org, all).
type groupable interface {
header() string
Expand Down Expand Up @@ -287,6 +319,14 @@ func (u userLabeler) label(e coder.Environment) string {
return fmt.Sprintf("[user: %s]", u.userMap[e.UserID].Email)
}

type providerLabeler struct {
providerMap map[string]coder.KubernetesProvider
}

func (p providerLabeler) label(e coder.Environment) string {
return fmt.Sprintf("[provider %s]", p.providerMap[e.ResourcePoolID].Name)
}

func aggregateEnvResources(envs []coder.Environment) resources {
var aggregate resources
for _, e := range envs {
Expand Down