Skip to content

Commit da94690

Browse files
wrap github api errors with grpc codes
1 parent 45165f4 commit da94690

File tree

11 files changed

+174
-138
lines changed

11 files changed

+174
-138
lines changed

go.sum

Lines changed: 72 additions & 0 deletions
Large diffs are not rendered by default.

pkg/connector/api_token.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import (
88
"github.com/conductorone/baton-sdk/pkg/annotations"
99
"github.com/conductorone/baton-sdk/pkg/pagination"
1010
resourceSdk "github.com/conductorone/baton-sdk/pkg/types/resource"
11-
"github.com/conductorone/baton-sdk/pkg/uhttp"
1211
"github.com/google/go-github/v69/github"
13-
"google.golang.org/grpc/codes"
1412
)
1513

1614
func apiTokenResource(ctx context.Context, token *github.PersonalAccessToken) (*v2.Resource, error) {
@@ -94,10 +92,7 @@ func (o *apiTokenResourceType) List(
9492
},
9593
})
9694
if err != nil {
97-
if isRatelimited(resp) {
98-
return nil, "", nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
99-
}
100-
return nil, "", nil, err
95+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to list fine-grained personal access tokens")
10196
}
10297

10398
restApiRateLimit, err := extractRateLimitData(resp)

pkg/connector/connector.go

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ func (gh *GitHub) Validate(ctx context.Context) (annotations.Annotations, error)
206206
if len(gh.enterprises) > 0 {
207207
_, _, err := gh.customClient.ListEnterpriseConsumedLicenses(ctx, gh.enterprises[0], 0)
208208
if err != nil {
209-
return nil, fmt.Errorf("can't list enterprise consumed licenses: %w", err)
209+
return nil, uhttp.WrapErrors(codes.PermissionDenied, "github-connector: failed to access enterprise licenses", err)
210210
}
211211
}
212212
return nil, nil
@@ -218,9 +218,9 @@ func (gh *GitHub) validateAppCredentials(ctx context.Context) (annotations.Annot
218218
return nil, fmt.Errorf("github-connector: only one org is allowed when using github app")
219219
}
220220

221-
_, _, err := findInstallation(ctx, gh.appClient, orgLogins[0])
221+
_, resp, err := findInstallation(ctx, gh.appClient, orgLogins[0])
222222
if err != nil {
223-
return nil, fmt.Errorf("github-connector: failed to retrieve org: %w", err)
223+
return nil, wrapGitHubError(err, resp, "github-connector: failed to retrieve org installation")
224224
}
225225
return nil, nil
226226
}
@@ -272,9 +272,9 @@ func New(ctx context.Context, ghc *cfg.Github, appKey string) (*GitHub, error) {
272272
if err != nil {
273273
return nil, err
274274
}
275-
installation, _, err := findInstallation(ctx, appClient, ghc.Orgs[0])
275+
installation, resp, err := findInstallation(ctx, appClient, ghc.Orgs[0])
276276
if err != nil {
277-
return nil, err
277+
return nil, wrapGitHubError(err, resp, "github-connector: failed to find app installation")
278278
}
279279

280280
token, err := getInstallationToken(ctx, appClient, installation.GetID())
@@ -453,16 +453,7 @@ func getOrgs(ctx context.Context, client *github.Client, orgs []string) ([]strin
453453
for {
454454
orgs, resp, err := client.Organizations.List(ctx, "", &github.ListOptions{Page: page, PerPage: maxPageSize})
455455
if err != nil {
456-
if isRatelimited(resp) {
457-
return nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
458-
}
459-
if isAuthError(resp) {
460-
return nil, uhttp.WrapErrors(codes.Unauthenticated, "github-connector: failed to retrieve org", err)
461-
}
462-
if isPermissionError(resp) {
463-
return nil, uhttp.WrapErrors(codes.PermissionDenied, "github-connector: failed to retrieve org", err)
464-
}
465-
return nil, fmt.Errorf("github-connector: failed to retrieve org: %w", err)
456+
return nil, wrapGitHubError(err, resp, "github-connector: failed to retrieve organizations")
466457
}
467458
if resp.StatusCode == http.StatusUnauthorized {
468459
return nil, status.Error(codes.Unauthenticated, "github token is not authorized")

pkg/connector/enterprise_role.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (o *enterpriseRoleResourceType) fillCache(ctx context.Context) error {
6060
for continuePagination {
6161
consumedLicenses, _, err := o.customClient.ListEnterpriseConsumedLicenses(ctx, enterprise, page)
6262
if err != nil {
63-
return fmt.Errorf("baton-github: error listing enterprise consumed licenses for %s: %w", enterprise, err)
63+
return uhttp.WrapErrors(codes.PermissionDenied, fmt.Sprintf("baton-github: error listing enterprise consumed licenses for %s", enterprise), err)
6464
}
6565

6666
if len(consumedLicenses.Users) == 0 {
@@ -141,10 +141,7 @@ func (o *enterpriseRoleResourceType) Grants(
141141
for _, userLogin := range cache[resource.Id.Resource] {
142142
user, resp, err := o.client.Users.Get(ctx, userLogin)
143143
if err != nil {
144-
if isRatelimited(resp) {
145-
return nil, "", nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
146-
}
147-
return nil, "", nil, fmt.Errorf("baton-github: error getting user %s: %w", userLogin, err)
144+
return nil, "", nil, wrapGitHubError(err, resp, fmt.Sprintf("baton-github: failed to get user %s", userLogin))
148145
}
149146

150147
principalId, err := resourceSdk.NewResourceID(resourceTypeUser, *user.ID)

pkg/connector/helpers.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import (
1111
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
1212
"github.com/conductorone/baton-sdk/pkg/annotations"
1313
"github.com/conductorone/baton-sdk/pkg/pagination"
14+
"github.com/conductorone/baton-sdk/pkg/uhttp"
1415
"github.com/google/go-github/v69/github"
1516
"github.com/shurcooL/githubv4"
1617
"golang.org/x/text/cases"
1718
"golang.org/x/text/language"
19+
"google.golang.org/grpc/codes"
1820
"google.golang.org/protobuf/types/known/timestamppb"
1921
)
2022

@@ -249,3 +251,23 @@ func isPermissionError(resp *github.Response) bool {
249251
}
250252
return resp.StatusCode == http.StatusForbidden
251253
}
254+
255+
// wrapGitHubError wraps GitHub API errors with appropriate gRPC status codes based on the HTTP response.
256+
// It handles rate limiting, authentication errors, permission errors, and generic errors.
257+
// The contextMsg parameter should describe the operation that failed (e.g., "failed to list teams").
258+
func wrapGitHubError(err error, resp *github.Response, contextMsg string) error {
259+
if err == nil {
260+
return nil
261+
}
262+
263+
if isRatelimited(resp) {
264+
return uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
265+
}
266+
if isAuthError(resp) {
267+
return uhttp.WrapErrors(codes.Unauthenticated, contextMsg, err)
268+
}
269+
if isPermissionError(resp) {
270+
return uhttp.WrapErrors(codes.PermissionDenied, contextMsg, err)
271+
}
272+
return fmt.Errorf("%s: %w", contextMsg, err)
273+
}

pkg/connector/invitation.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (i *invitationResourceType) List(ctx context.Context, parentID *v2.Resource
7474
if isNotFoundError(resp) {
7575
return nil, "", nil, nil
7676
}
77-
return nil, "", nil, fmt.Errorf("github-connector: ListPendingOrgInvitatioins failed: %w", err)
77+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to list pending org invitations")
7878
}
7979

8080
restApiRateLimit, err := extractRateLimitData(resp)
@@ -140,7 +140,7 @@ func (i *invitationResourceType) CreateAccount(
140140
Email: params.email,
141141
})
142142
if err != nil {
143-
return nil, nil, nil, fmt.Errorf("github-connectorv2: failed to invite user to org: %w", err)
143+
return nil, nil, nil, wrapGitHubError(err, resp, "github-connector: failed to create org invitation")
144144
}
145145

146146
restApiRateLimit, err := extractRateLimitData(resp)

pkg/connector/org.go

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,7 @@ func (o *orgResourceType) List(
102102

103103
orgs, resp, err := o.client.Organizations.List(ctx, "", opts)
104104
if err != nil {
105-
if isRatelimited(resp) {
106-
return nil, "", nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
107-
}
108-
return nil, "", nil, fmt.Errorf("github-connector: failed to fetch org: %w", err)
105+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to fetch organizations")
109106
}
110107

111108
nextPage, reqAnnos, err := parseResp(resp)
@@ -129,11 +126,7 @@ func (o *orgResourceType) List(
129126
l.Warn("insufficient access to list org membership, skipping org", zap.String("org", org.GetLogin()))
130127
continue
131128
}
132-
133-
if isRatelimited(resp) {
134-
return nil, "", nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
135-
}
136-
return nil, "", nil, err
129+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to get org membership")
137130
}
138131

139132
// Only sync orgs that we are an admin for
@@ -227,11 +220,7 @@ func (o *orgResourceType) Grants(
227220
if isNotFoundError(resp) {
228221
return nil, "", nil, uhttp.WrapErrors(codes.NotFound, fmt.Sprintf("org: %s not found", orgName))
229222
}
230-
errMsg := "github-connectorv2: failed to list org members"
231-
if isRatelimited(resp) {
232-
return nil, "", nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
233-
}
234-
return nil, "", nil, fmt.Errorf("%s: %w", errMsg, err)
223+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to list org members")
235224
}
236225

237226
var nextPage string
@@ -294,9 +283,9 @@ func (o *orgResourceType) Grant(ctx context.Context, principal *v2.Resource, en
294283
return nil, err
295284
}
296285

297-
user, _, err := o.client.Users.GetByID(ctx, principalID)
286+
user, resp, err := o.client.Users.GetByID(ctx, principalID)
298287
if err != nil {
299-
return nil, fmt.Errorf("github-connectorv2: failed to get user: %w", err)
288+
return nil, wrapGitHubError(err, resp, "github-connector: failed to get user")
300289
}
301290

302291
requestedRole := ""
@@ -309,21 +298,21 @@ func (o *orgResourceType) Grant(ctx context.Context, principal *v2.Resource, en
309298
return nil, fmt.Errorf("github-connectorv2: invalid entitlement id: %s", en.Id)
310299
}
311300

312-
isMember, _, err := o.client.Organizations.IsMember(ctx, orgName, user.GetLogin())
301+
isMember, resp, err := o.client.Organizations.IsMember(ctx, orgName, user.GetLogin())
313302
if err != nil {
314-
return nil, fmt.Errorf("github-connectorv2: failed to get org membership: %w", err)
303+
return nil, wrapGitHubError(err, resp, "github-connector: failed to check org membership")
315304
}
316305

317306
// TODO: check existing invitations. Duplicate invitations aren't allowed, so this will fail with 4xx from github.
318307

319308
// If user isn't a member, invite them to the org with the requested role
320309
if !isMember {
321-
_, _, err = o.client.Organizations.CreateOrgInvitation(ctx, orgName, &github.CreateOrgInvitationOptions{
310+
_, resp, err = o.client.Organizations.CreateOrgInvitation(ctx, orgName, &github.CreateOrgInvitationOptions{
322311
InviteeID: user.ID,
323312
Role: &requestedRole,
324313
})
325314
if err != nil {
326-
return nil, fmt.Errorf("github-connectorv2: failed to invite user to org: %w", err)
315+
return nil, wrapGitHubError(err, resp, "github-connector: failed to invite user to org")
327316
}
328317
return nil, nil
329318
}
@@ -334,9 +323,9 @@ func (o *orgResourceType) Grant(ctx context.Context, principal *v2.Resource, en
334323
}
335324

336325
// If the user is a member, check to see what role they have
337-
membership, _, err := o.client.Organizations.GetOrgMembership(ctx, user.GetLogin(), orgName)
326+
membership, resp, err := o.client.Organizations.GetOrgMembership(ctx, user.GetLogin(), orgName)
338327
if err != nil {
339-
return nil, fmt.Errorf("github-connectorv2: failed to get org membership: %w", err)
328+
return nil, wrapGitHubError(err, resp, "github-connector: failed to get org membership")
340329
}
341330

342331
// Skip if user already has requested role
@@ -346,9 +335,9 @@ func (o *orgResourceType) Grant(ctx context.Context, principal *v2.Resource, en
346335
}
347336

348337
// User is a member but grant is for admin, so make them an admin.
349-
_, _, err = o.client.Organizations.EditOrgMembership(ctx, user.GetLogin(), orgName, &github.Membership{Role: github.Ptr(orgRoleAdmin)})
338+
_, resp, err = o.client.Organizations.EditOrgMembership(ctx, user.GetLogin(), orgName, &github.Membership{Role: github.Ptr(orgRoleAdmin)})
350339
if err != nil {
351-
return nil, fmt.Errorf("github-connectorv2: failed to make user an admin : %w", err)
340+
return nil, wrapGitHubError(err, resp, "github-connector: failed to make user an admin")
352341
}
353342

354343
return nil, nil
@@ -386,31 +375,31 @@ func (o *orgResourceType) Revoke(ctx context.Context, grant *v2.Grant) (annotati
386375
return nil, err
387376
}
388377

389-
user, _, err := o.client.Users.GetByID(ctx, principalID)
378+
user, resp, err := o.client.Users.GetByID(ctx, principalID)
390379
if err != nil {
391-
return nil, fmt.Errorf("github-connectorv2: failed to get user: %w", err)
380+
return nil, wrapGitHubError(err, resp, "github-connector: failed to get user")
392381
}
393382

394-
membership, _, err := o.client.Organizations.GetOrgMembership(ctx, user.GetLogin(), orgName)
383+
membership, resp, err := o.client.Organizations.GetOrgMembership(ctx, user.GetLogin(), orgName)
395384
if err != nil {
396-
return nil, fmt.Errorf("github-connectorv2: failed to get org membership: %w", err)
385+
return nil, wrapGitHubError(err, resp, "github-connector: failed to get org membership")
397386
}
398387

399388
if membership.GetState() != "active" {
400389
return nil, fmt.Errorf("github-connectorv2: user is not an active member of the org")
401390
}
402391

403392
if en.Id == memberRoleID {
404-
_, err = o.client.Organizations.RemoveOrgMembership(ctx, user.GetLogin(), orgName)
393+
resp, err = o.client.Organizations.RemoveOrgMembership(ctx, user.GetLogin(), orgName)
405394
if err != nil {
406-
return nil, fmt.Errorf("github-connectorv2: failed to revoke org membership from user: %w", err)
395+
return nil, wrapGitHubError(err, resp, "github-connector: failed to revoke org membership from user")
407396
}
408397
return nil, nil
409398
}
410399

411-
_, _, err = o.client.Organizations.EditOrgMembership(ctx, user.GetLogin(), orgName, &github.Membership{Role: github.Ptr(orgRoleMember)})
400+
_, resp, err = o.client.Organizations.EditOrgMembership(ctx, user.GetLogin(), orgName, &github.Membership{Role: github.Ptr(orgRoleMember)})
412401
if err != nil {
413-
return nil, fmt.Errorf("github-connectorv2: failed to revoke org admin from user: %w", err)
402+
return nil, wrapGitHubError(err, resp, "github-connector: failed to revoke org admin from user")
414403
}
415404

416405
return nil, nil
@@ -449,7 +438,7 @@ func (o *orgResourceType) listOrganizationsFromAppInstallations(
449438
for orgName := range o.orgs {
450439
org, resp, err = o.client.Organizations.Get(ctx, orgName)
451440
if err != nil {
452-
return nil, "", nil, fmt.Errorf("github-connector: failed to fetch organization: %w", err)
441+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to fetch organization")
453442
}
454443
}
455444

pkg/connector/org_role.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ import (
1313
"github.com/conductorone/baton-sdk/pkg/types/entitlement"
1414
"github.com/conductorone/baton-sdk/pkg/types/grant"
1515
"github.com/conductorone/baton-sdk/pkg/types/resource"
16-
"github.com/conductorone/baton-sdk/pkg/uhttp"
1716
"github.com/google/go-github/v69/github"
1817
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
1918
"go.uber.org/zap"
20-
"google.golang.org/grpc/codes"
2119
)
2220

2321
type OrganizationRole struct {
@@ -90,10 +88,7 @@ func (o *orgRoleResourceType) List(
9088
// Return empty list with no error to indicate we skipped this resource
9189
return nil, "", nil, nil
9290
}
93-
if isRatelimited(resp) {
94-
return nil, "", nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
95-
}
96-
return nil, "", nil, fmt.Errorf("failed to list organization roles: %w", err)
91+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to list organization roles")
9792
}
9893

9994
var ret []*v2.Resource
@@ -180,7 +175,7 @@ func (o *orgRoleResourceType) Grants(
180175
}
181176
return rv, pageToken, nil, nil
182177
}
183-
return nil, "", nil, fmt.Errorf("failed to list role users: %w", err)
178+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to list users assigned to org role")
184179
}
185180
nextPage, respAnnos, err := parseResp(resp)
186181
if err != nil {
@@ -227,10 +222,7 @@ func (o *orgRoleResourceType) Grants(
227222
}
228223
return nil, pageToken, nil, nil
229224
}
230-
if isRatelimited(resp) {
231-
return nil, "", nil, uhttp.WrapErrors(codes.Unavailable, "too many requests", err)
232-
}
233-
return nil, "", nil, fmt.Errorf("failed to list role teams: %w", err)
225+
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to list teams assigned to org role")
234226
}
235227

236228
nextPage, respAnnos, err := parseResp(resp)

0 commit comments

Comments
 (0)