@@ -6,12 +6,14 @@ import (
66 "encoding/json"
77 "fmt"
88 "os"
9+ "strings"
910 "sync"
1011
1112 nodev1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1"
1213 "connectrpc.com/connect"
1314
1415 "github.com/brevdev/brev-cli/pkg/analytics"
16+ "github.com/brevdev/brev-cli/pkg/auth"
1517 "github.com/brevdev/brev-cli/pkg/externalnode"
1618
1719 "github.com/brevdev/brev-cli/pkg/cmd/cmderrors"
@@ -37,6 +39,7 @@ import (
3739type LsStore interface {
3840 GetWorkspaces (organizationID string , options * store.GetWorkspacesOptions ) ([]entity.Workspace , error )
3941 GetActiveOrganizationOrDefault () (* entity.Organization , error )
42+ GetCachedActiveOrganizationOrNil () (* entity.Organization , error )
4043 GetCurrentUser () (* entity.User , error )
4144 GetUsers (queryParams map [string ]string ) ([]entity.User , error )
4245 GetWorkspace (workspaceID string ) (* entity.Workspace , error )
@@ -75,10 +78,13 @@ with other commands like stop, start, or delete.`,
7578 brev ls orgs --json
7679 ` ,
7780 PersistentPostRunE : func (cmd * cobra.Command , args []string ) error {
81+ if isAPIKeyAuthStore (noLoginLsStore ) {
82+ return cmdcontext .InvokeParentPersistentPostRun (cmd , args )
83+ }
7884 if hello .ShouldWeRunOnboardingLSStep (noLoginLsStore ) && hello .ShouldWeRunOnboarding (noLoginLsStore ) {
7985 // Getting the workspaces should go in the hello.go file but then
8086 // requires passing in stores and that makes it hard to use in other commands
81- org , err := getOrgForRunLs (loginLsStore , org )
87+ org , err := getOrgForRunLs (loginLsStore , org , false )
8288 if err != nil {
8389 return err
8490 }
@@ -146,9 +152,11 @@ with other commands like stop, start, or delete.`,
146152// trackLsAnalytics sends analytics event for ls command
147153func trackLsAnalytics (store LsStore ) {
148154 userID := ""
149- user , err := store .GetCurrentUser ()
150- if err == nil {
151- userID = user .ID
155+ if ! isAPIKeyAuthStore (store ) {
156+ user , err := store .GetCurrentUser ()
157+ if err == nil {
158+ userID = user .ID
159+ }
152160 }
153161 data := analytics.EventData {
154162 EventName : "Brev ls" ,
@@ -157,8 +165,30 @@ func trackLsAnalytics(store LsStore) {
157165 _ = analytics .TrackEvent (data )
158166}
159167
160- func getOrgForRunLs (lsStore LsStore , orgflag string ) (* entity.Organization , error ) {
168+ func isAPIKeyAuthStore (lsStore LsStore ) bool {
169+ token , err := lsStore .GetAccessToken ()
170+ if err != nil {
171+ return false
172+ }
173+ return strings .HasPrefix (strings .TrimSpace (token ), auth .BrevAPIKeyPrefix )
174+ }
175+
176+ func getOrgForRunLs (lsStore LsStore , orgflag string , apiKeyAuth bool ) (* entity.Organization , error ) {
161177 var org * entity.Organization
178+ if apiKeyAuth {
179+ if orgflag != "" {
180+ return nil , breverrors .NewValidationError ("api key auth is scoped to the org saved during login; --org is not supported" )
181+ }
182+ cachedOrg , err := lsStore .GetCachedActiveOrganizationOrNil ()
183+ if err != nil {
184+ return nil , breverrors .WrapAndTrace (err )
185+ }
186+ if cachedOrg == nil || cachedOrg .ID == "" {
187+ return nil , breverrors .NewValidationError ("api key auth requires an active org; run brev login --api-key <api-key>::<org-id>" )
188+ }
189+ return cachedOrg , nil
190+ }
191+
162192 if orgflag != "" {
163193 var orgs []entity.Organization
164194 orgs , err := lsStore .GetOrganizations (& store.GetOrganizationsOptions {Name : orgflag })
@@ -188,12 +218,18 @@ func getOrgForRunLs(lsStore LsStore, orgflag string) (*entity.Organization, erro
188218
189219func RunLs (t * terminal.Terminal , lsStore LsStore , args []string , orgflag string , showAll bool , jsonOutput bool ) error {
190220 ls := NewLs (lsStore , t , jsonOutput )
191- user , err := lsStore .GetCurrentUser ()
192- if err != nil {
193- return breverrors .WrapAndTrace (err )
221+ apiKeyAuth := isAPIKeyAuthStore (lsStore )
222+
223+ var user * entity.User
224+ if ! apiKeyAuth {
225+ var err error
226+ user , err = lsStore .GetCurrentUser ()
227+ if err != nil {
228+ return breverrors .WrapAndTrace (err )
229+ }
194230 }
195231
196- org , err := getOrgForRunLs (lsStore , orgflag )
232+ org , err := getOrgForRunLs (lsStore , orgflag , apiKeyAuth )
197233 if err != nil {
198234 return breverrors .WrapAndTrace (err )
199235 }
@@ -202,7 +238,7 @@ func RunLs(t *terminal.Terminal, lsStore LsStore, args []string, orgflag string,
202238 }
203239
204240 if len (args ) == 1 { //nolint:gocritic // don't want to switch
205- err = handleLsArg (ls , args [0 ], user , org , showAll )
241+ err = handleLsArg (ls , args [0 ], user , org , showAll , apiKeyAuth )
206242 if err != nil {
207243 return breverrors .WrapAndTrace (err )
208244 }
@@ -218,10 +254,13 @@ func RunLs(t *terminal.Terminal, lsStore LsStore, args []string, orgflag string,
218254 return nil
219255}
220256
221- func handleLsArg (ls * Ls , arg string , user * entity.User , org * entity.Organization , showAll bool ) error {
257+ func handleLsArg (ls * Ls , arg string , user * entity.User , org * entity.Organization , showAll bool , apiKeyAuth bool ) error {
222258 // todo refactor this to cmd.register
223259 //nolint:gocritic // idk how to write this as a switch
224260 if util .IsSingularOrPlural (arg , "org" ) || util .IsSingularOrPlural (arg , "organization" ) { // handle org, orgs, and organization(s)
261+ if apiKeyAuth {
262+ return breverrors .NewValidationError ("api key auth cannot list organizations" )
263+ }
225264 err := ls .RunOrgs ()
226265 if err != nil {
227266 return breverrors .WrapAndTrace (err )
@@ -232,13 +271,25 @@ func handleLsArg(ls *Ls, arg string, user *entity.User, org *entity.Organization
232271 if err != nil {
233272 return breverrors .WrapAndTrace (err )
234273 }
235- } else if util .IsSingularOrPlural (arg , "user" ) && featureflag .IsAdmin (user .GlobalUserType ) {
274+ } else if util .IsSingularOrPlural (arg , "user" ) {
275+ if apiKeyAuth {
276+ return breverrors .NewValidationError ("api key auth cannot list users" )
277+ }
278+ if ! featureflag .IsAdmin (user .GlobalUserType ) {
279+ return nil
280+ }
236281 err := ls .RunUser (showAll )
237282 if err != nil {
238283 return breverrors .WrapAndTrace (err )
239284 }
240285 return nil
241- } else if util .IsSingularOrPlural (arg , "host" ) && featureflag .IsAdmin (user .GlobalUserType ) {
286+ } else if util .IsSingularOrPlural (arg , "host" ) {
287+ if apiKeyAuth {
288+ return breverrors .NewValidationError ("api key auth cannot list hosts" )
289+ }
290+ if ! featureflag .IsAdmin (user .GlobalUserType ) {
291+ return nil
292+ }
242293 err := ls .RunHosts (org )
243294 if err != nil {
244295 return breverrors .WrapAndTrace (err )
@@ -388,6 +439,19 @@ func (ls Ls) ShowUserWorkspaces(org *entity.Organization, otherOrgs []entity.Org
388439 ls .displayWorkspacesAndHelp (org , otherOrgs , userWorkspaces , allWorkspaces , gpuLookup )
389440}
390441
442+ func (ls Ls ) ShowOrgWorkspaces (org * entity.Organization , workspaces []entity.Workspace , gpuLookup map [string ]string ) {
443+ if len (workspaces ) == 0 {
444+ ls .terminal .Vprint (ls .terminal .Yellow ("No instances in org %s\n " , org .Name ))
445+ return
446+ }
447+ ls .terminal .Vprintf ("Org %s has %d instances\n " , ls .terminal .Yellow (org .Name ), len (workspaces ))
448+ displayWorkspacesTable (ls .terminal , workspaces , gpuLookup )
449+
450+ fmt .Print ("\n " )
451+
452+ displayLsResetBreadCrumb (ls .terminal , workspaces )
453+ }
454+
391455func (ls Ls ) displayWorkspacesAndHelp (org * entity.Organization , otherOrgs []entity.Organization , userWorkspaces []entity.Workspace , allWorkspaces []entity.Workspace , gpuLookup map [string ]string ) {
392456 if len (userWorkspaces ) == 0 {
393457 ls .terminal .Vprint (ls .terminal .Yellow ("No instances in org %s\n " , org .Name ))
@@ -489,6 +553,8 @@ func (ls Ls) RunWorkspaces(org *entity.Organization, user *entity.User, showAll
489553 var workspacesToShow []entity.Workspace
490554 if showAll {
491555 workspacesToShow = allWorkspaces
556+ } else if user == nil {
557+ workspacesToShow = allWorkspaces
492558 } else {
493559 workspacesToShow = store .FilterForUserWorkspaces (allWorkspaces , user .ID )
494560 }
@@ -498,6 +564,11 @@ func (ls Ls) RunWorkspaces(org *entity.Organization, user *entity.User, showAll
498564 return ls .outputWorkspacesJSON (workspacesToShow , gpuLookup , nodes )
499565 }
500566
567+ if user == nil {
568+ ls .ShowOrgWorkspaces (org , workspacesToShow , gpuLookup )
569+ return nil
570+ }
571+
501572 // Table output with colors and help text
502573 orgs , err := ls .lsStore .GetOrganizations (nil )
503574 if err != nil {
0 commit comments