Skip to content

Commit 21fbde5

Browse files
committed
add whoami command
1 parent 7dc79fc commit 21fbde5

2 files changed

Lines changed: 170 additions & 0 deletions

File tree

pkg/koyeb/koyeb.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func GetRootCommand() *cobra.Command {
118118
rootCmd.AddCommand(NewSnapshotCmd())
119119
rootCmd.AddCommand(NewComposeCmd())
120120
rootCmd.AddCommand(NewSandboxCmd())
121+
rootCmd.AddCommand(NewWhoAmICmd())
121122
return rootCmd
122123
}
123124

pkg/koyeb/whoami.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package koyeb
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
8+
"github.com/koyeb/koyeb-cli/pkg/koyeb/errors"
9+
"github.com/koyeb/koyeb-cli/pkg/koyeb/renderer"
10+
log "github.com/sirupsen/logrus"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func NewWhoAmICmd() *cobra.Command {
15+
return &cobra.Command{
16+
Use: "whoami",
17+
Short: "Show information about the currently authenticated user or organization",
18+
RunE: WithCLIContext(WhoAmI),
19+
}
20+
}
21+
22+
func WhoAmI(ctx *CLIContext, cmd *cobra.Command, args []string) error {
23+
full := GetBoolFlags(cmd, "full")
24+
25+
// Try to get the current user. This only succeeds with a user token.
26+
userRes, userResp, userErr := ctx.Client.ProfileApi.GetCurrentUser(ctx.Context).Execute()
27+
isUserToken := userErr == nil
28+
log.Debugf("whoami: GetCurrentUser: isUserToken=%v status=%s", isUserToken, httpStatus(userResp))
29+
log.Debugf("whoami: ctx.Organization=%q", ctx.Organization)
30+
31+
var org *koyeb.Organization
32+
33+
if ctx.Organization != "" {
34+
// Org ID is known (set via --organization flag or config).
35+
// GetOrganization works for any valid token and returns 401 on invalid ones.
36+
log.Debugf("whoami: fetching org via GetOrganization(%s)", ctx.Organization)
37+
orgRes, resp, err := ctx.Client.OrganizationApi.GetOrganization(ctx.Context, ctx.Organization).Execute()
38+
if err != nil {
39+
log.Debugf("whoami: GetOrganization failed: status=%s err=%v", httpStatus(resp), err)
40+
return errors.NewCLIErrorFromAPIError("Error while retrieving the current organization", err, resp)
41+
}
42+
o := orgRes.GetOrganization()
43+
org = &o
44+
} else if isUserToken {
45+
// User token: the profile endpoint returns the current org and errors on invalid tokens.
46+
log.Debugf("whoami: fetching org via GetCurrentOrganization")
47+
orgRes, resp, err := ctx.Client.ProfileApi.GetCurrentOrganization(ctx.Context).Execute()
48+
if err != nil {
49+
log.Debugf("whoami: GetCurrentOrganization failed: status=%s err=%v", httpStatus(resp), err)
50+
return errors.NewCLIErrorFromAPIError("Error while retrieving the current organization", err, resp)
51+
}
52+
o := orgRes.GetOrganization()
53+
org = &o
54+
} else {
55+
// Direct org API key with no org ID in context.
56+
// Discover the org ID from apps (which always carry organization_id).
57+
// A 401 here means the token is invalid or missing.
58+
log.Debugf("whoami: org token with no org ID in context — discovering via ListApps")
59+
appsRes, resp, err := ctx.Client.AppsApi.ListApps(ctx.Context).Limit("1").Execute()
60+
if err != nil {
61+
log.Debugf("whoami: ListApps failed: status=%s err=%v", httpStatus(resp), err)
62+
return errors.NewCLIErrorFromAPIError("Error while validating the token", err, resp)
63+
}
64+
apps := appsRes.GetApps()
65+
if len(apps) > 0 {
66+
if orgId := apps[0].GetOrganizationId(); orgId != "" {
67+
log.Debugf("whoami: discovered org ID %q from apps", orgId)
68+
orgRes, resp, err := ctx.Client.OrganizationApi.GetOrganization(ctx.Context, orgId).Execute()
69+
if err != nil {
70+
log.Debugf("whoami: GetOrganization(%s) failed: status=%s err=%v", orgId, httpStatus(resp), err)
71+
} else {
72+
o := orgRes.GetOrganization()
73+
org = &o
74+
}
75+
}
76+
} else {
77+
log.Debugf("whoami: no apps found, org fields will be empty")
78+
}
79+
}
80+
81+
var user *koyeb.User
82+
if isUserToken {
83+
u := userRes.GetUser()
84+
user = &u
85+
}
86+
87+
ctx.Renderer.Render(NewWhoAmIReply(user, org, full))
88+
return nil
89+
}
90+
91+
func httpStatus(resp *http.Response) string {
92+
if resp == nil {
93+
return "no response"
94+
}
95+
return http.StatusText(resp.StatusCode)
96+
}
97+
98+
type WhoAmIReply struct {
99+
user *koyeb.User
100+
org *koyeb.Organization
101+
full bool
102+
}
103+
104+
func NewWhoAmIReply(user *koyeb.User, org *koyeb.Organization, full bool) *WhoAmIReply {
105+
return &WhoAmIReply{user: user, org: org, full: full}
106+
}
107+
108+
func (WhoAmIReply) Title() string {
109+
return "Identity"
110+
}
111+
112+
func (r *WhoAmIReply) MarshalBinary() ([]byte, error) {
113+
tokenType := "organization"
114+
if r.user != nil {
115+
tokenType = "user"
116+
}
117+
out := map[string]interface{}{
118+
"token_type": tokenType,
119+
"org_id": "",
120+
"org_name": "",
121+
"plan": "",
122+
"org_status": "",
123+
}
124+
if r.user != nil {
125+
out["user_id"] = r.user.GetId()
126+
out["name"] = r.user.GetName()
127+
out["email"] = r.user.GetEmail()
128+
}
129+
if r.org != nil {
130+
out["org_id"] = r.org.GetId()
131+
out["org_name"] = r.org.GetName()
132+
out["plan"] = string(r.org.GetPlan())
133+
out["org_status"] = string(r.org.GetStatus())
134+
}
135+
return json.Marshal(out)
136+
}
137+
138+
func (r *WhoAmIReply) Headers() []string {
139+
if r.user != nil {
140+
return []string{"token_type", "org_id", "org_name", "plan", "org_status", "user_id", "name", "email"}
141+
}
142+
return []string{"token_type", "org_id", "org_name", "plan", "org_status"}
143+
}
144+
145+
func (r *WhoAmIReply) Fields() []map[string]string {
146+
tokenType := "organization"
147+
if r.user != nil {
148+
tokenType = "user"
149+
}
150+
fields := map[string]string{
151+
"token_type": tokenType,
152+
"org_id": "",
153+
"org_name": "",
154+
"plan": "",
155+
"org_status": "",
156+
}
157+
if r.user != nil {
158+
fields["user_id"] = renderer.FormatID(r.user.GetId(), r.full)
159+
fields["name"] = r.user.GetName()
160+
fields["email"] = r.user.GetEmail()
161+
}
162+
if r.org != nil {
163+
fields["org_id"] = renderer.FormatID(r.org.GetId(), r.full)
164+
fields["org_name"] = r.org.GetName()
165+
fields["plan"] = string(r.org.GetPlan())
166+
fields["org_status"] = string(r.org.GetStatus())
167+
}
168+
return []map[string]string{fields}
169+
}

0 commit comments

Comments
 (0)