Skip to content

Commit 81f7555

Browse files
Fix broken oauth login flow (#15)
## Problem Currently, when you try and use the `pinecone login` command the login fails after confirming authentication in your browser because of a panic: ![Screenshot 2024-08-16 at 4 35 13 PM](https://github.com/user-attachments/assets/762cb70c-0d0f-4ba2-b764-1935d85420f4) We're unsafely accessing a slice. Our decoding of the JSON responses from the `dashboard` API are failing because the shape of some responses has changed. We've had several people run into this issue: - https://pinecone-io.slack.com/archives/C06V9RSCBLG/p1723806488866219 - https://pinecone-io.slack.com/archives/C06V9RSCBLG/p1721693472202859 ## Solution I looked at the `dashboard` repo to try and figure out what changed along with directly dumping the response bodies in the CLI locally. It seems the json naming and structure of how `organizations` and `projects` are returned has changed slightly, and projects are no longer explicitly nested under organizations. Specifically, this value in the response is now `newOrgs` rather than `organizations`: https://github.com/pinecone-io/dashboard/blob/f31f9b126781adf6bf30eb5f137cc0d983ba691b/api-server/functions/src/api/dashboard/organizations/organizations.router.ts#L48 It also seems like there was a `globalProject` field being returned inside of each `project` previously, and that's no longer the case. Looks like the fields were flattened into `project`. - Get rid of `GlobalProject` type and move fields into `Project` which is what we get back from the `/organizations` functions now. - Update the json marshaling in `OrganizationResponse` to ``json:"newOrgs"`` to properly decode the response. - Add logic to re-nest `Projects` inside of `Organizations` when the `ListOrganizations` response is returned. ### Follow Up Our reliance on the `dashboard` APIs is somewhat brittle because we're not always aware when changes to the API interface happen. We need to improve this process moving forward, and work to make the CLI more robust in how it handles failures. ## Type of Change - [X] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update - [ ] Infrastructure change (CI configs, etc) - [ ] Non-code change (docs, etc) - [ ] None of the above: (explain here) ## Test Plan You should be able to pull this branch down, use `goreleaser` to build the CLI locally, and then attempt logging in: ``` goreleaser build --single-target --snapshot --clean ./dist/pinecone_darwin_arm64/pinecone login ``` Here's the testing flow from my end: Login: ![Screenshot 2024-08-16 at 4 45 03 PM](https://github.com/user-attachments/assets/ba15ad3a-926e-4eb3-8432-7a570bcbcce1) Target Organization / Project: ![Screenshot 2024-08-16 at 4 45 21 PM](https://github.com/user-attachments/assets/fdfd38cd-ba80-485e-907a-721bf806379f) List Indexes: ![Screenshot 2024-08-16 at 4 45 28 PM](https://github.com/user-attachments/assets/78471764-970b-4f95-a86e-dde1582991ac) --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1207873887567497
1 parent 51bbc6b commit 81f7555

File tree

7 files changed

+43
-27
lines changed

7 files changed

+43
-27
lines changed

internal/pkg/cli/command/project/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func NewCreateProjectCmd() *cobra.Command {
4444
msg.FailMsg("Failed to create project %s\n", style.Emphasis(options.name))
4545
exit.Error(pcio.Errorf("Create project call returned 200 but with success=false in the body%s", options.name))
4646
}
47-
msg.SuccessMsg("Project %s created successfully.\n", style.Emphasis(proj.GlobalProject.Name))
47+
msg.SuccessMsg("Project %s created successfully.\n", style.Emphasis(proj.Project.Name))
4848
},
4949
}
5050

internal/pkg/cli/command/project/list.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func printTable(projects []dashboard.Project) {
112112
pcio.Fprint(writer, header)
113113

114114
for _, proj := range projects {
115-
values := []string{proj.GlobalProject.Id, proj.Name}
115+
values := []string{proj.Id, proj.Name}
116116
pcio.Fprintf(writer, strings.Join(values, "\t")+"\n")
117117
}
118118
writer.Flush()
@@ -127,7 +127,7 @@ func printTableAll(orgs *dashboard.OrganizationsResponse) {
127127

128128
for _, org := range orgs.Organizations {
129129
for _, proj := range org.Projects {
130-
values := []string{org.Id, org.Name, proj.Name, proj.GlobalProject.Id}
130+
values := []string{org.Id, org.Name, proj.Name, proj.Id}
131131
pcio.Fprintf(writer, strings.Join(values, "\t")+"\n")
132132
}
133133
}

internal/pkg/cli/command/target/target.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func NewTargetCmd() *cobra.Command {
153153
}
154154
state.TargetProj.Set(&state.TargetProject{
155155
Name: proj.Name,
156-
Id: proj.GlobalProject.Id,
156+
Id: proj.Id,
157157
})
158158
}
159159

@@ -260,7 +260,7 @@ func postLoginSetupTargetProject(orgs *dashboard.OrganizationsResponse, targetOr
260260
} else if len(org.Projects) == 1 {
261261
state.TargetProj.Set(&state.TargetProject{
262262
Name: org.Projects[0].Name,
263-
Id: org.Projects[0].GlobalProject.Id,
263+
Id: org.Projects[0].Id,
264264
})
265265
return org.Projects[0].Name
266266
} else {
@@ -274,7 +274,7 @@ func postLoginSetupTargetProject(orgs *dashboard.OrganizationsResponse, targetOr
274274
if proj.Name == projectName {
275275
state.TargetProj.Set(&state.TargetProject{
276276
Name: proj.Name,
277-
Id: proj.GlobalProject.Id,
277+
Id: proj.Id,
278278
})
279279
return proj.Name
280280
}

internal/pkg/dashboard/keys_list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const (
2020
URL_GET_API_KEYS = "/v2/dashboard/projects/%s/api-keys"
2121
)
2222

23-
func GetApiKeys(project GlobalProject) (*KeyResponse, error) {
23+
func GetApiKeys(project Project) (*KeyResponse, error) {
2424
return GetApiKeysById(project.Id)
2525
}
2626

internal/pkg/dashboard/organizations_list.go

+28-12
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const (
1010
)
1111

1212
type OrganizationsResponse struct {
13-
Organizations []Organization `json:"organizations"`
13+
Organizations []Organization `json:"newOrgs"`
14+
Projects []Project `json:"projects"`
1415
}
1516

1617
type Organization struct {
@@ -20,16 +21,11 @@ type Organization struct {
2021
}
2122

2223
type Project struct {
23-
Id string `json:"id"`
24-
Name string `json:"name"`
25-
GlobalProject GlobalProject `json:"globalProject"`
26-
}
27-
28-
type GlobalProject struct {
29-
Id string `json:"id"`
30-
Name string `json:"name"`
31-
Quota string `json:"quota"`
32-
IndexQuota string `json:"indexQuota"`
24+
Id string `json:"id"`
25+
Name string `json:"name"`
26+
OrganizationId string `json:"organization_id"`
27+
Quota string `json:"quota"`
28+
IndexQuota string `json:"index_quota"`
3329
}
3430

3531
func ListOrganizations() (*OrganizationsResponse, error) {
@@ -42,16 +38,36 @@ func ListOrganizations() (*OrganizationsResponse, error) {
4238
if err != nil {
4339
return nil, err
4440
}
45-
for _, org := range resp.Organizations {
41+
42+
orgToProjectsMap := make(map[string][]Project)
43+
44+
// Organize projects into orgs to match the older data structure
45+
for _, project := range resp.Projects {
46+
if projects, ok := orgToProjectsMap[project.OrganizationId]; ok {
47+
projects = append(projects, project)
48+
orgToProjectsMap[project.OrganizationId] = projects
49+
} else {
50+
orgToProjectsMap[project.OrganizationId] = []Project{project}
51+
}
52+
}
53+
54+
// Modify the response to nest projects under their orgs
55+
for i := range resp.Organizations {
56+
org := &resp.Organizations[i]
57+
4658
log.Trace().
4759
Str("org", string(org.Name)).
4860
Msg("found org")
61+
62+
org.Projects = orgToProjectsMap[org.Id]
63+
4964
for _, proj := range org.Projects {
5065
log.Trace().
5166
Str("org", string(org.Name)).
5267
Str("project", proj.Name).
5368
Msg("found project in org")
5469
}
5570
}
71+
5672
return resp, nil
5773
}

internal/pkg/dashboard/projects_create.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ type CreateProjectRequest struct {
1616
}
1717

1818
type CreateProjectResponse struct {
19-
Success bool `json:"success"`
20-
GlobalProject GlobalProject `json:"globalProject"`
19+
Success bool `json:"success"`
20+
Project Project `json:"globalProject"`
2121
}
2222

2323
func CreateProject(orgId string, projName string, podQuota int32) (*CreateProjectResponse, error) {

internal/pkg/dashboard/projects_get.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ import (
44
"github.com/pinecone-io/cli/internal/pkg/utils/pcio"
55
)
66

7-
func GetProjectByName(orgName string, projName string) (*GlobalProject, error) {
7+
func GetProjectByName(orgName string, projName string) (*Project, error) {
88
orgs, err := ListOrganizations()
99
if err != nil {
1010
return nil, err
1111
}
1212
for _, org := range orgs.Organizations {
1313
if org.Name == orgName {
1414
for _, proj := range org.Projects {
15-
if proj.GlobalProject.Name == projName {
16-
return &proj.GlobalProject, nil
15+
if proj.Name == projName {
16+
return &proj, nil
1717
}
1818
}
1919
}
2020
}
2121
return nil, error(pcio.Errorf("project name %s not found in organization %s", projName, orgName))
2222
}
2323

24-
func GetProjectById(orgId string, projId string) (*GlobalProject, error) {
24+
func GetProjectById(orgId string, projId string) (*Project, error) {
2525
orgs, err := ListOrganizations()
2626
if err != nil {
2727
return nil, err
@@ -30,8 +30,8 @@ func GetProjectById(orgId string, projId string) (*GlobalProject, error) {
3030
for _, org := range orgs.Organizations {
3131
if org.Id == orgId {
3232
for _, proj := range org.Projects {
33-
if proj.GlobalProject.Id == projId {
34-
return &proj.GlobalProject, nil
33+
if proj.Id == projId {
34+
return &proj, nil
3535
}
3636
}
3737
}

0 commit comments

Comments
 (0)