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

Commit 1969750

Browse files
committed
Initial prototype of resources command
1 parent d765c36 commit 1969750

File tree

5 files changed

+156
-12
lines changed

5 files changed

+156
-12
lines changed

coder-sdk/env.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Environment struct {
2121
UserID string `json:"user_id" tab:"-"`
2222
LastBuiltAt time.Time `json:"last_built_at" tab:"-"`
2323
CPUCores float32 `json:"cpu_cores" tab:"CPUCores"`
24-
MemoryGB int `json:"memory_gb" tab:"MemoryGB"`
24+
MemoryGB float32 `json:"memory_gb" tab:"MemoryGB"`
2525
DiskGB int `json:"disk_gb" tab:"DiskGB"`
2626
GPUs int `json:"gpus" tab:"GPUs"`
2727
Updating bool `json:"updating" tab:"Updating"`
@@ -93,6 +93,16 @@ func (c Client) CreateEnvironment(ctx context.Context, orgID string, req CreateE
9393
return &env, nil
9494
}
9595

96+
// ListEnvironments lists environments returned by the given filter.
97+
// TODO: add the filter options
98+
func (c Client) ListEnvironments(ctx context.Context) ([]Environment, error) {
99+
var envs []Environment
100+
if err := c.requestBody(ctx, http.MethodGet, "/api/environments", nil, &envs); err != nil {
101+
return nil, err
102+
}
103+
return envs, nil
104+
}
105+
96106
// EnvironmentsByOrganization gets the list of environments owned by the given user.
97107
func (c Client) EnvironmentsByOrganization(ctx context.Context, userID, orgID string) ([]Environment, error) {
98108
var envs []Environment

coder-sdk/org.go

+35-8
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,47 @@ package coder
33
import (
44
"context"
55
"net/http"
6+
"time"
67
)
78

8-
// Org describes an Organization in Coder
9-
type Org struct {
10-
ID string `json:"id"`
11-
Name string `json:"name"`
12-
Members []User `json:"members"`
9+
// Organization describes an Organization in Coder
10+
type Organization struct {
11+
ID string `json:"id"`
12+
Name string `json:"name"`
13+
Members []OrganizationUser `json:"members"`
1314
}
1415

15-
// Orgs gets all Organizations
16-
func (c Client) Orgs(ctx context.Context) ([]Org, error) {
17-
var orgs []Org
16+
// OrganizationUser user wraps the basic User type and adds data specific to the user's membership of an organization
17+
type OrganizationUser struct {
18+
User
19+
OrganizationRoles []OrganizationRole `json:"organization_roles"`
20+
RolesUpdatedAt time.Time `json:"roles_updated_at"`
21+
}
22+
23+
// OrganizationRole defines an organization OrganizationRole
24+
type OrganizationRole string
25+
26+
// The OrganizationRole enum values
27+
const (
28+
RoleOrgMember OrganizationRole = "organization-member"
29+
RoleOrgAdmin OrganizationRole = "organization-admin"
30+
RoleOrgManager OrganizationRole = "organization-manager"
31+
)
32+
33+
// Organizations gets all Organizations
34+
func (c Client) Organizations(ctx context.Context) ([]Organization, error) {
35+
var orgs []Organization
1836
if err := c.requestBody(ctx, http.MethodGet, "/api/orgs", nil, &orgs); err != nil {
1937
return nil, err
2038
}
2139
return orgs, nil
2240
}
41+
42+
// OrgMembers get all members of the given organization
43+
func (c Client) OrgMembers(ctx context.Context, orgID string) ([]OrganizationUser, error) {
44+
var members []OrganizationUser
45+
if err := c.requestBody(ctx, http.MethodGet, "/api/orgs/"+orgID+"/members", nil, &members); err != nil {
46+
return nil, err
47+
}
48+
return members, nil
49+
}

internal/cmd/ceapi.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import (
1212
// Helpers for working with the Coder Enterprise API.
1313

1414
// lookupUserOrgs gets a list of orgs the user is apart of.
15-
func lookupUserOrgs(user *coder.User, orgs []coder.Org) []coder.Org {
15+
func lookupUserOrgs(user *coder.User, orgs []coder.Organization) []coder.Organization {
1616
// NOTE: We don't know in advance how many orgs the user is in so we can't pre-alloc.
17-
var userOrgs []coder.Org
17+
var userOrgs []coder.Organization
1818

1919
for _, org := range orgs {
2020
for _, member := range org.Members {
@@ -36,7 +36,7 @@ func getEnvs(ctx context.Context, client *coder.Client, email string) ([]coder.E
3636
return nil, xerrors.Errorf("get user: %w", err)
3737
}
3838

39-
orgs, err := client.Orgs(ctx)
39+
orgs, err := client.Organizations(ctx)
4040
if err != nil {
4141
return nil, xerrors.Errorf("get orgs: %w", err)
4242
}

internal/cmd/cmd.go

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func Make() *cobra.Command {
2424
makeEnvsCommand(),
2525
makeSyncCmd(),
2626
makeURLCmd(),
27+
makeResourceCmd(),
2728
completionCmd,
2829
genDocs(app),
2930
)

internal/cmd/resourcemanager.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"text/tabwriter"
7+
8+
"cdr.dev/coder-cli/coder-sdk"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func makeResourceCmd() *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "resources",
15+
Short: "manager Coder resources with platform-level context (users, organizations, environments)",
16+
}
17+
cmd.AddCommand(resourceTop)
18+
return cmd
19+
}
20+
21+
var resourceTop = &cobra.Command{
22+
Use: "top",
23+
RunE: func(cmd *cobra.Command, args []string) error {
24+
ctx := cmd.Context()
25+
26+
client, err := newClient()
27+
if err != nil {
28+
return err
29+
}
30+
31+
envs, err := client.ListEnvironments(ctx)
32+
if err != nil {
33+
return err
34+
}
35+
36+
userEnvs := make(map[string][]coder.Environment)
37+
for _, e := range envs {
38+
userEnvs[e.UserID] = append(userEnvs[e.UserID], e)
39+
}
40+
41+
users, err := client.Users(ctx)
42+
if err != nil {
43+
return err
44+
}
45+
46+
orgs := make(map[string]coder.Organization)
47+
orglist, err := client.Organizations(ctx)
48+
if err != nil {
49+
return err
50+
}
51+
for _, o := range orglist {
52+
orgs[o.ID] = o
53+
}
54+
55+
tabwriter := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
56+
for _, u := range users {
57+
_, _ = fmt.Fprintf(tabwriter, "%s\t(%s)\t%s", u.Name, u.Email, aggregateEnvResources(userEnvs[u.ID]))
58+
if len(userEnvs[u.ID]) > 0 {
59+
_, _ = fmt.Fprintf(tabwriter, "\f")
60+
}
61+
for _, env := range userEnvs[u.ID] {
62+
_, _ = fmt.Fprintf(tabwriter, "\t")
63+
_, _ = fmt.Fprintln(tabwriter, fmtEnvResources(env, orgs))
64+
}
65+
fmt.Fprint(tabwriter, "\n")
66+
}
67+
_ = tabwriter.Flush()
68+
69+
return nil
70+
},
71+
}
72+
73+
func resourcesFromEnv(env coder.Environment) resources {
74+
return resources{
75+
cpuAllocation: env.CPUCores,
76+
cpuUtilization: env.LatestStat.CPUUsage,
77+
memAllocation: env.MemoryGB,
78+
memUtilization: env.LatestStat.MemoryUsage,
79+
}
80+
}
81+
82+
func fmtEnvResources(env coder.Environment, orgs map[string]coder.Organization) string {
83+
return fmt.Sprintf("%s\t%s\t[org: %s]", env.Name, resourcesFromEnv(env), orgs[env.OrganizationID].Name)
84+
}
85+
86+
func aggregateEnvResources(envs []coder.Environment) resources {
87+
var aggregate resources
88+
for _, e := range envs {
89+
aggregate.cpuAllocation += e.CPUCores
90+
aggregate.cpuUtilization += e.LatestStat.CPUUsage
91+
aggregate.memAllocation += e.MemoryGB
92+
aggregate.memUtilization += e.LatestStat.MemoryUsage
93+
}
94+
return aggregate
95+
}
96+
97+
type resources struct {
98+
cpuAllocation float32
99+
cpuUtilization float32
100+
memAllocation float32
101+
memUtilization float32
102+
}
103+
104+
func (a resources) String() string {
105+
return fmt.Sprintf("[cpu: alloc=%.1fvCPU, util=%.1f]\t[mem: alloc=%.1fGB, util=%.1f]", a.cpuAllocation, a.cpuUtilization, a.memAllocation, a.memUtilization)
106+
}

0 commit comments

Comments
 (0)