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

Lines changed: 11 additions & 1 deletion
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

Lines changed: 35 additions & 8 deletions
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

Lines changed: 3 additions & 3 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 106 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy