@@ -23,6 +23,7 @@ func makeResourceCmd() *cobra.Command {
23
23
}
24
24
25
25
func resourceTop ()* cobra.Command {
26
+ var group string
26
27
cmd := & cobra.Command {
27
28
Use :"top" ,
28
29
RunE :func (cmd * cobra.Command ,args []string )error {
@@ -39,64 +40,124 @@ func resourceTop() *cobra.Command {
39
40
return xerrors .Errorf ("get environments %w" ,err )
40
41
}
41
42
42
- userEnvs := make (map [string ][]coder.Environment )
43
- for _ ,e := range envs {
44
- userEnvs [e .UserID ]= append (userEnvs [e .UserID ],e )
45
- }
46
-
47
43
users ,err := client .Users (ctx )
48
44
if err != nil {
49
45
return xerrors .Errorf ("get users: %w" ,err )
50
46
}
51
47
52
- orgIDMap := make (map [string ]coder.Organization )
53
- orglist ,err := client .Organizations (ctx )
48
+ orgs ,err := client .Organizations (ctx )
54
49
if err != nil {
55
50
return xerrors .Errorf ("get organizations: %w" ,err )
56
51
}
57
- for _ ,o := range orglist {
58
- orgIDMap [o .ID ]= o
52
+
53
+ var groups []groupable
54
+ var labeler envLabeler
55
+ switch group {
56
+ case "user" :
57
+ userEnvs := make (map [string ][]coder.Environment ,len (users ))
58
+ for _ ,e := range envs {
59
+ userEnvs [e .UserID ]= append (userEnvs [e .UserID ],e )
60
+ }
61
+ for _ ,u := range users {
62
+ groups = append (groups ,userGrouping {user :u ,envs :userEnvs [u .ID ]})
63
+ }
64
+ orgIDMap := make (map [string ]coder.Organization )
65
+ for _ ,o := range orgs {
66
+ orgIDMap [o .ID ]= o
67
+ }
68
+ labeler = orgLabeler {orgIDMap }
69
+ case "org" :
70
+ orgEnvs := make (map [string ][]coder.Environment ,len (orgs ))
71
+ for _ ,e := range envs {
72
+ orgEnvs [e .OrganizationID ]= append (orgEnvs [e .OrganizationID ],e )
73
+ }
74
+ for _ ,o := range orgs {
75
+ groups = append (groups ,orgGrouping {org :o ,envs :orgEnvs [o .ID ]})
76
+ }
77
+ userIDMap := make (map [string ]coder.User )
78
+ for _ ,u := range users {
79
+ userIDMap [u .ID ]= u
80
+ }
81
+ labeler = userLabeler {userIDMap }
82
+ default :
83
+ return xerrors .Errorf ("unknown --group %q" ,group )
59
84
}
60
85
61
- printResourceTop (os .Stdout ,users , orgIDMap , userEnvs )
86
+ printResourceTop (os .Stdout ,groups , labeler )
62
87
return nil
63
88
},
64
89
}
90
+ cmd .Flags ().StringVar (& group ,"group" ,"user" ,"the grouping parameter (user|org|all)" )
65
91
66
92
return cmd
67
93
}
68
94
69
- func printResourceTop (writer io.Writer ,users []coder.User ,orgIDMap map [string ]coder.Organization ,userEnvs map [string ][]coder.Environment ) {
95
+ // groupable specifies a structure capable of being an aggregation group of environments (user, org, all)
96
+ type groupable interface {
97
+ header ()string
98
+ environments () []coder.Environment
99
+ }
100
+
101
+ type userGrouping struct {
102
+ user coder.User
103
+ envs []coder.Environment
104
+ }
105
+
106
+ func (u userGrouping )environments () []coder.Environment {
107
+ return u .envs
108
+ }
109
+
110
+ func (u userGrouping )header ()string {
111
+ return fmt .Sprintf ("%s\t (%s)" ,truncate (u .user .Name ,20 ,"..." ),u .user .Email )
112
+ }
113
+
114
+ type orgGrouping struct {
115
+ org coder.Organization
116
+ envs []coder.Environment
117
+ }
118
+
119
+ func (o orgGrouping )environments () []coder.Environment {
120
+ return o .envs
121
+ }
122
+
123
+ func (o orgGrouping )header ()string {
124
+ plural := "s"
125
+ if len (o .org .Members )< 2 {
126
+ plural = ""
127
+ }
128
+ return fmt .Sprintf ("%s\t (%v member%s)" ,truncate (o .org .Name ,20 ,"..." ),len (o .org .Members ),plural )
129
+ }
130
+
131
+ func printResourceTop (writer io.Writer ,groups []groupable ,labeler envLabeler ) {
70
132
tabwriter := tabwriter .NewWriter (writer ,0 ,0 ,4 ,' ' ,0 )
71
133
defer func () {_ = tabwriter .Flush () }()
72
134
73
- var userResources []aggregatedUser
74
- for _ ,u := range users {
135
+ var userResources []aggregatedResources
136
+ for _ ,group := range groups {
75
137
// truncate user names to ensure tabwriter doesn't push our entire table too far
76
- u .Name = truncate (u .Name ,20 ,"..." )
77
- userResources = append (userResources ,aggregatedUser {User :u ,resources :aggregateEnvResources (userEnvs [u .ID ])})
138
+ userResources = append (userResources ,aggregatedResources {groupable :group ,resources :aggregateEnvResources (group .environments ())})
78
139
}
79
140
sort .Slice (userResources ,func (i ,j int )bool {
80
141
return userResources [i ].cpuAllocation > userResources [j ].cpuAllocation
81
142
})
82
143
83
144
for _ ,u := range userResources {
84
- _ ,_ = fmt .Fprintf (tabwriter ,"%s\t (%s) \t %s " ,u .Name , u . Email ,u .resources )
145
+ _ ,_ = fmt .Fprintf (tabwriter ,"%s\t %s " ,u .header () ,u .resources )
85
146
if verbose {
86
- if len (userEnvs [ u . ID ] )> 0 {
147
+ if len (u . environments () )> 0 {
87
148
_ ,_ = fmt .Fprintf (tabwriter ,"\f " )
88
149
}
89
- for _ ,env := range userEnvs [ u . ID ] {
150
+ for _ ,env := range u . environments () {
90
151
_ ,_ = fmt .Fprintf (tabwriter ,"\t " )
91
- _ ,_ = fmt .Fprintln (tabwriter ,fmtEnvResources (env ,orgIDMap ))
152
+ _ ,_ = fmt .Fprintln (tabwriter ,fmtEnvResources (env ,labeler ))
92
153
}
93
154
}
94
155
_ ,_ = fmt .Fprint (tabwriter ,"\n " )
95
156
}
96
157
}
97
158
98
- type aggregatedUser struct {
99
- coder. User
159
+ type aggregatedResources struct {
160
+ groupable
100
161
resources
101
162
}
102
163
@@ -109,8 +170,28 @@ func resourcesFromEnv(env coder.Environment) resources {
109
170
}
110
171
}
111
172
112
- func fmtEnvResources (env coder.Environment ,orgs map [string ]coder.Organization )string {
113
- return fmt .Sprintf ("%s\t %s\t [org: %s]" ,env .Name ,resourcesFromEnv (env ),orgs [env .OrganizationID ].Name )
173
+ func fmtEnvResources (env coder.Environment ,labeler envLabeler )string {
174
+ return fmt .Sprintf ("%s\t %s\t %s" ,env .Name ,resourcesFromEnv (env ),labeler .label (env ))
175
+ }
176
+
177
+ type envLabeler interface {
178
+ label (coder.Environment )string
179
+ }
180
+
181
+ type orgLabeler struct {
182
+ orgMap map [string ]coder.Organization
183
+ }
184
+
185
+ func (o orgLabeler )label (e coder.Environment )string {
186
+ return fmt .Sprintf ("[org: %s]" ,o .orgMap [e .OrganizationID ].Name )
187
+ }
188
+
189
+ type userLabeler struct {
190
+ userMap map [string ]coder.User
191
+ }
192
+
193
+ func (u userLabeler )label (e coder.Environment )string {
194
+ return fmt .Sprintf ("[user: %s]" ,u .userMap [e .UserID ].Email )
114
195
}
115
196
116
197
func aggregateEnvResources (envs []coder.Environment )resources {