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

Initial prototype of resources command #137

Merged
merged 6 commits into from
Oct 18, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Initial prototype of resources command
  • Loading branch information
cmoog committed Oct 18, 2020
commit d138de860ba4cbb5fe87ae3928f4668d4093277a
12 changes: 11 additions & 1 deletion coder-sdk/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type Environment struct {
UserID string `json:"user_id" tab:"-"`
LastBuiltAt time.Time `json:"last_built_at" tab:"-"`
CPUCores float32 `json:"cpu_cores" tab:"CPUCores"`
MemoryGB int `json:"memory_gb" tab:"MemoryGB"`
MemoryGB float32 `json:"memory_gb" tab:"MemoryGB"`
DiskGB int `json:"disk_gb" tab:"DiskGB"`
GPUs int `json:"gpus" tab:"GPUs"`
Updating bool `json:"updating" tab:"Updating"`
Expand Down Expand Up @@ -93,6 +93,16 @@ func (c Client) CreateEnvironment(ctx context.Context, orgID string, req CreateE
return &env, nil
}

// ListEnvironments lists environments returned by the given filter.
// TODO: add the filter options
func (c Client) ListEnvironments(ctx context.Context) ([]Environment, error) {
var envs []Environment
if err := c.requestBody(ctx, http.MethodGet, "/api/environments", nil, &envs); err != nil {
return nil, err
}
return envs, nil
}

// EnvironmentsByOrganization gets the list of environments owned by the given user.
func (c Client) EnvironmentsByOrganization(ctx context.Context, userID, orgID string) ([]Environment, error) {
var envs []Environment
Expand Down
43 changes: 35 additions & 8 deletions coder-sdk/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,47 @@ package coder
import (
"context"
"net/http"
"time"
)

// Org describes an Organization in Coder
type Org struct {
ID string `json:"id"`
Name string `json:"name"`
Members []User `json:"members"`
// Organization describes an Organization in Coder
type Organization struct {
ID string `json:"id"`
Name string `json:"name"`
Members []OrganizationUser `json:"members"`
}

// Orgs gets all Organizations
func (c Client) Orgs(ctx context.Context) ([]Org, error) {
var orgs []Org
// OrganizationUser user wraps the basic User type and adds data specific to the user's membership of an organization
type OrganizationUser struct {
User
OrganizationRoles []OrganizationRole `json:"organization_roles"`
RolesUpdatedAt time.Time `json:"roles_updated_at"`
}

// OrganizationRole defines an organization OrganizationRole
type OrganizationRole string

// The OrganizationRole enum values
const (
RoleOrgMember OrganizationRole = "organization-member"
RoleOrgAdmin OrganizationRole = "organization-admin"
RoleOrgManager OrganizationRole = "organization-manager"
)

// Organizations gets all Organizations
func (c Client) Organizations(ctx context.Context) ([]Organization, error) {
var orgs []Organization
if err := c.requestBody(ctx, http.MethodGet, "/api/orgs", nil, &orgs); err != nil {
return nil, err
}
return orgs, nil
}

// OrgMembers get all members of the given organization
func (c Client) OrgMembers(ctx context.Context, orgID string) ([]OrganizationUser, error) {
var members []OrganizationUser
if err := c.requestBody(ctx, http.MethodGet, "/api/orgs/"+orgID+"/members", nil, &members); err != nil {
return nil, err
}
return members, nil
}
6 changes: 3 additions & 3 deletions internal/cmd/ceapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
// Helpers for working with the Coder Enterprise API.

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

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

orgs, err := client.Orgs(ctx)
orgs, err := client.Organizations(ctx)
if err != nil {
return nil, xerrors.Errorf("get orgs: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func Make() *cobra.Command {
makeEnvsCommand(),
makeSyncCmd(),
makeURLCmd(),
makeResourceCmd(),
completionCmd,
genDocs(app),
)
Expand Down
106 changes: 106 additions & 0 deletions internal/cmd/resourcemanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package cmd

import (
"fmt"
"os"
"text/tabwriter"

"cdr.dev/coder-cli/coder-sdk"
"github.com/spf13/cobra"
)

func makeResourceCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "resources",
Short: "manager Coder resources with platform-level context (users, organizations, environments)",
}
cmd.AddCommand(resourceTop)
return cmd
}

var resourceTop = &cobra.Command{
Use: "top",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

client, err := newClient()
if err != nil {
return err
}

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

userEnvs := make(map[string][]coder.Environment)
for _, e := range envs {
userEnvs[e.UserID] = append(userEnvs[e.UserID], e)
}

users, err := client.Users(ctx)
if err != nil {
return err
}

orgs := make(map[string]coder.Organization)
orglist, err := client.Organizations(ctx)
if err != nil {
return err
}
for _, o := range orglist {
orgs[o.ID] = o
}

tabwriter := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
for _, u := range users {
_, _ = fmt.Fprintf(tabwriter, "%s\t(%s)\t%s", u.Name, u.Email, aggregateEnvResources(userEnvs[u.ID]))
if len(userEnvs[u.ID]) > 0 {
_, _ = fmt.Fprintf(tabwriter, "\f")
}
for _, env := range userEnvs[u.ID] {
_, _ = fmt.Fprintf(tabwriter, "\t")
_, _ = fmt.Fprintln(tabwriter, fmtEnvResources(env, orgs))
}
fmt.Fprint(tabwriter, "\n")
}
_ = tabwriter.Flush()

return nil
},
}

func resourcesFromEnv(env coder.Environment) resources {
return resources{
cpuAllocation: env.CPUCores,
cpuUtilization: env.LatestStat.CPUUsage,
memAllocation: env.MemoryGB,
memUtilization: env.LatestStat.MemoryUsage,
}
}

func fmtEnvResources(env coder.Environment, orgs map[string]coder.Organization) string {
return fmt.Sprintf("%s\t%s\t[org: %s]", env.Name, resourcesFromEnv(env), orgs[env.OrganizationID].Name)
}

func aggregateEnvResources(envs []coder.Environment) resources {
var aggregate resources
for _, e := range envs {
aggregate.cpuAllocation += e.CPUCores
aggregate.cpuUtilization += e.LatestStat.CPUUsage
aggregate.memAllocation += e.MemoryGB
aggregate.memUtilization += e.LatestStat.MemoryUsage
}
return aggregate
}

type resources struct {
cpuAllocation float32
cpuUtilization float32
memAllocation float32
memUtilization float32
}

func (a resources) String() string {
return fmt.Sprintf("[cpu: alloc=%.1fvCPU, util=%.1f]\t[mem: alloc=%.1fGB, util=%.1f]", a.cpuAllocation, a.cpuUtilization, a.memAllocation, a.memUtilization)
}