Skip to content
60 changes: 2 additions & 58 deletions cmd/baton-github/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@ package main

import (
"context"
"fmt"
"os"

cfg "github.com/conductorone/baton-github/pkg/config"
"github.com/conductorone/baton-sdk/pkg/config"
"github.com/conductorone/baton-sdk/pkg/connectorbuilder"
"github.com/conductorone/baton-sdk/pkg/field"
"github.com/conductorone/baton-sdk/pkg/types"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"go.uber.org/zap"
"github.com/conductorone/baton-sdk/pkg/connectorrunner"

"github.com/conductorone/baton-github/pkg/connector"
)
Expand All @@ -20,55 +14,5 @@ var version = "dev"

func main() {
ctx := context.Background()

_, cmd, err := config.DefineConfiguration(
ctx,
"baton-github",
getConnector,
cfg.Config,
)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
cmd.Version = version

err = cmd.Execute()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}

func getConnector(ctx context.Context, ghc *cfg.Github) (types.ConnectorServer, error) {
l := ctxzap.Extract(ctx)

err := field.Validate(cfg.Config, ghc)
if err != nil {
return nil, err
}

privateKey := ""
if ghc.AppPrivatekeyPath != "" {
keyBytes, err := os.ReadFile(ghc.AppPrivatekeyPath)
if err != nil {
l.Error("error reading app private key file", zap.Error(err), zap.String("appPrivateKeyPath", ghc.AppPrivatekeyPath))
return nil, fmt.Errorf("failed to read app private key file: %w", err)
}
privateKey = string(keyBytes)
}

cb, err := connector.New(ctx, ghc, privateKey)
if err != nil {
l.Error("error creating connector", zap.Error(err))
return nil, err
}

c, err := connectorbuilder.NewConnector(ctx, cb)
if err != nil {
l.Error("error creating connector", zap.Error(err))
return nil, err
}

return c, nil
config.RunConnector(ctx, "baton-github", version, cfg.Config, connector.NewLambdaConnector, connectorrunner.WithSessionStoreEnabled())
}
29 changes: 16 additions & 13 deletions pkg/config/conf.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 35 additions & 12 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import (
"github.com/conductorone/baton-sdk/pkg/field"
)

const (
GithubAppGroup = "github-app-group"
GithubPersonalAccessTokenGroup = "personal-access-token-group"
)

// TODO (mb): Make sure we don't need field.WithRequired(true) for required fields.
var (
accessTokenField = field.StringField(
"token",
field.WithDisplayName("Personal access token"),
field.WithDescription("The GitHub access token used to connect to the GitHub API."),
field.WithIsSecret(true),
field.WithRequired(true),
)
orgsField = field.StringSliceField(
"orgs",
Expand All @@ -31,13 +37,18 @@ var (
"app-id",
field.WithDisplayName("GitHub App ID"),
field.WithDescription("The GitHub App to connect to."),
field.WithRequired(true),
)

appPrivateKeyPath = field.StringField(
appPrivateKeyPath = field.FileUploadField(
"app-privatekey-path",
[]string{".pem"},
field.WithDisplayName("GitHub App private key (.pem)"),
field.WithDescription("Path to private key that is used to connect to the GitHub App"),
field.WithIsSecret(true),
field.WithRequired(true),
)

syncSecrets = field.BoolField(
"sync-secrets",
field.WithDisplayName("Sync secrets"),
Expand All @@ -48,16 +59,12 @@ var (
field.WithDisplayName("Omit syncing archived repositories"),
field.WithDescription("Whether to skip syncing archived repositories or not"),
)
fieldRelationships = []field.SchemaFieldRelationship{
field.FieldsMutuallyExclusive(
accessTokenField,
appPrivateKeyPath,
),
field.FieldsRequiredTogether(
appPrivateKeyPath,
appIDField,
),
}
orgField = field.StringField(
"org",
field.WithDisplayName("Github App Organization"),
field.WithDescription("Organization of your github app"),
field.WithRequired(true),
)
)

//go:generate go run ./gen
Expand All @@ -71,9 +78,25 @@ var Config = field.NewConfiguration(
omitArchivedRepositories,
appIDField,
appPrivateKeyPath,
orgField,
},
field.WithConstraints(fieldRelationships...),
field.WithConnectorDisplayName("GitHub v2"),
field.WithHelpUrl("/docs/baton/github-v2"),
field.WithIconUrl("/static/app-icons/github.svg"),
field.WithFieldGroups([]field.SchemaFieldGroup{
{
Name: GithubPersonalAccessTokenGroup,
DisplayName: "Personal access token",
HelpText: "Use a personal access token for authentication.",
Fields: []field.SchemaField{accessTokenField, orgsField, omitArchivedRepositories},
Default: true,
},
{
Name: GithubAppGroup,
DisplayName: "GitHub app",
HelpText: "Use a github app for authentication",
Fields: []field.SchemaField{appIDField, appPrivateKeyPath, orgField, syncSecrets, omitArchivedRepositories},
Default: false,
},
}),
)
43 changes: 24 additions & 19 deletions pkg/connector/api_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
"github.com/conductorone/baton-sdk/pkg/pagination"
resourceSdk "github.com/conductorone/baton-sdk/pkg/types/resource"
"github.com/google/go-github/v69/github"
)
Expand Down Expand Up @@ -55,72 +54,78 @@ func (o *apiTokenResourceType) ResourceType(_ context.Context) *v2.ResourceType
return o.resourceType
}

func (o *apiTokenResourceType) Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
func (o *apiTokenResourceType) Entitlements(ctx context.Context, resource *v2.Resource, opts resourceSdk.SyncOpAttrs) ([]*v2.Entitlement, *resourceSdk.SyncOpResults, error) {
// API Token secrets do not have entitlements
return nil, "", nil, nil
return nil, &resourceSdk.SyncOpResults{}, nil
}

func (o *apiTokenResourceType) Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
func (o *apiTokenResourceType) Grants(ctx context.Context, resource *v2.Resource, opts resourceSdk.SyncOpAttrs) ([]*v2.Grant, *resourceSdk.SyncOpResults, error) {
// API Token secrets do not have grants
return nil, "", nil, nil
return nil, &resourceSdk.SyncOpResults{}, nil
}

func (o *apiTokenResourceType) List(
ctx context.Context,
parentID *v2.ResourceId,
pToken *pagination.Token,
) ([]*v2.Resource, string, annotations.Annotations, error) {
opts resourceSdk.SyncOpAttrs,
) ([]*v2.Resource, *resourceSdk.SyncOpResults, error) {
var annotations annotations.Annotations
if parentID == nil {
return nil, "", nil, nil
return nil, &resourceSdk.SyncOpResults{}, nil
}

bag, page, err := parsePageToken(pToken.Token, &v2.ResourceId{ResourceType: resourceTypeApiToken.Id})
bag, page, err := parsePageToken(opts.PageToken.Token, &v2.ResourceId{ResourceType: resourceTypeApiToken.Id})
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

orgName, err := o.orgCache.GetOrgName(ctx, parentID)
orgName, err := o.orgCache.GetOrgName(ctx, opts.Session, parentID)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

tokens, resp, err := o.client.Organizations.ListFineGrainedPersonalAccessTokens(ctx, orgName, &github.ListFineGrainedPATOptions{
ListOptions: github.ListOptions{
Page: page,
PerPage: pToken.Size,
PerPage: opts.PageToken.Size,
},
})
if err != nil {
return nil, "", nil, wrapGitHubError(err, resp, "github-connector: failed to list fine-grained personal access tokens")
return nil, nil, wrapGitHubError(err, resp, "github-connector: failed to list fine-grained personal access tokens")
}

restApiRateLimit, err := extractRateLimitData(resp)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}
annotations.WithRateLimiting(restApiRateLimit)

nextPage, _, err := parseResp(resp)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

pageToken, err := bag.NextToken(nextPage)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

var rv []*v2.Resource
for _, t := range tokens {
resource, err := apiTokenResource(ctx, t)
if err != nil {
return nil, pageToken, annotations, err
return nil, &resourceSdk.SyncOpResults{
NextPageToken: pageToken,
Annotations: annotations,
}, err
}
Comment on lines 114 to 121
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling pattern.

When apiTokenResource fails within the loop, this code returns a non-nil SyncOpResults along with the error. This differs from all other error paths in this file and other resource types, which return (nil, nil, err). Returning partial results with an error can cause unexpected behavior in callers that may not expect both a result and an error.

🐛 Proposed fix for consistent error handling
 	for _, t := range tokens {
 		resource, err := apiTokenResource(ctx, t)
 		if err != nil {
-			return nil, &resourceSdk.SyncOpResults{
-				NextPageToken: pageToken,
-				Annotations:   annotations,
-			}, err
+			return nil, nil, err
 		}
 		rv = append(rv, resource)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, t := range tokens {
resource, err := apiTokenResource(ctx, t)
if err != nil {
return nil, pageToken, annotations, err
return nil, &resourceSdk.SyncOpResults{
NextPageToken: pageToken,
Annotations: annotations,
}, err
}
for _, t := range tokens {
resource, err := apiTokenResource(ctx, t)
if err != nil {
return nil, nil, err
}
rv = append(rv, resource)
}
🤖 Prompt for AI Agents
In `@pkg/connector/api_token.go` around lines 114 - 121, The loop over tokens
calls apiTokenResource(ctx, t) and on error currently returns a non-nil
resourceSdk.SyncOpResults together with err; change this to match the file's
standard pattern by returning (nil, nil, err) when apiTokenResource fails.
Locate the for _, t := range tokens loop in pkg/connector/api_token.go and
update the error branch that references apiTokenResource, pageToken and
annotations so it returns nil, nil, err instead of returning a partial
SyncOpResults.

rv = append(rv, resource)
}

return rv, pageToken, annotations, nil
return rv, &resourceSdk.SyncOpResults{
NextPageToken: pageToken,
Annotations: annotations,
}, nil
}

func apiTokenBuilder(client *github.Client, hasSAMLEnabled *bool, orgCache *orgNameCache) *apiTokenResourceType {
Expand Down
Loading
Loading