From 55d8b0f780c44f4e73e22cd1d7f7bbda7cc7626f Mon Sep 17 00:00:00 2001 From: Paul Querna Date: Sun, 31 May 2026 16:11:00 +0000 Subject: [PATCH 1/5] nhi: add GitHub Apps syncer (APP_REGISTRATION) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Apps and their installations were never synced — the connector authenticates as a GitHub App (mints the App JWT, finds the org installation) but discarded that identity. Add a read-only `app` resource type that enumerates org-installed GitHub Apps via `GET /orgs/{org}/installations` (Organizations.ListInstallations) and emits each as a non-human identity: WithNHIType(NHI_TYPE_APP_REGISTRATION, "github.app") Each app carries TRAIT_APP plus a profile (app_id, app_slug, installation_id, account_login, target_type, repository_selection). The endpoint requires Organization administration (read); when the configured credentials lack it (e.g. a GitHub App installation token), the syncer logs a warning and skips that org rather than failing the whole sync. Bumps baton-sdk v0.10.0 -> v0.11.0 for WithNHIType and re-vendors. Co-authored-by: c1-squire-dev[bot] --- pkg/connector/app.go | 163 +++++++++++++++++++++++++++++++++++++ pkg/connector/app_test.go | 47 +++++++++++ pkg/connector/connector.go | 7 ++ pkg/connector/org.go | 1 + 4 files changed, 218 insertions(+) create mode 100644 pkg/connector/app.go create mode 100644 pkg/connector/app_test.go diff --git a/pkg/connector/app.go b/pkg/connector/app.go new file mode 100644 index 00000000..2ebc182c --- /dev/null +++ b/pkg/connector/app.go @@ -0,0 +1,163 @@ +package connector + +import ( + "context" + "fmt" + "net/http" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + resourceSdk "github.com/conductorone/baton-sdk/pkg/types/resource" + "github.com/google/go-github/v69/github" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" +) + +// appResource builds a GitHub App resource from one organization installation. +// The org installations endpoint is the only org-wide-enumerable window onto +// GitHub Apps: each installation row carries the app's identity (app_id, +// app_slug) plus the installed scope. The resource is keyed by installation ID +// (unique and stable per org) and carries TRAIT_APP + an APP_REGISTRATION NHI +// annotation so c1 classifies it as a non-human identity. +func appResource(ctx context.Context, installation *github.Installation, parentResourceID *v2.ResourceId) (*v2.Resource, error) { + appSlug := installation.GetAppSlug() + displayName := appSlug + if displayName == "" { + displayName = fmt.Sprintf("app-%d", installation.GetAppID()) + } + + profile := map[string]interface{}{ + "app_id": installation.GetAppID(), + "app_slug": appSlug, + "installation_id": installation.GetID(), + } + if account := installation.GetAccount(); account != nil { + profile["account_login"] = account.GetLogin() + } + if installation.TargetType != nil { + profile["target_type"] = installation.GetTargetType() + } + if installation.RepositorySelection != nil { + profile["repository_selection"] = installation.GetRepositorySelection() + } + + annos := []proto.Message{ + &v2.V1Identifier{Id: fmt.Sprintf("app:%d", installation.GetID())}, + } + if installation.HTMLURL != nil { + annos = append(annos, &v2.ExternalLink{Url: installation.GetHTMLURL()}) + } + + return resourceSdk.NewResource( + displayName, + resourceTypeApp, + installation.GetID(), + resourceSdk.WithParentResourceID(parentResourceID), + resourceSdk.WithAppTrait(resourceSdk.WithAppProfile(profile)), + resourceSdk.WithNHIType(v2.NonHumanIdentityTrait_NHI_TYPE_APP_REGISTRATION, "github.app"), + resourceSdk.WithAnnotation(annos...), + ) +} + +type appResourceType struct { + resourceType *v2.ResourceType + client *github.Client + orgCache *orgNameCache +} + +func (o *appResourceType) ResourceType(_ context.Context) *v2.ResourceType { + return o.resourceType +} + +func (o *appResourceType) Entitlements(ctx context.Context, resource *v2.Resource, opts resourceSdk.SyncOpAttrs) ([]*v2.Entitlement, *resourceSdk.SyncOpResults, error) { + // GitHub Apps are synced read-only as NHI app registrations; no entitlements. + return nil, &resourceSdk.SyncOpResults{}, nil +} + +func (o *appResourceType) Grants(ctx context.Context, resource *v2.Resource, opts resourceSdk.SyncOpAttrs) ([]*v2.Grant, *resourceSdk.SyncOpResults, error) { + // GitHub Apps are synced read-only as NHI app registrations; no grants. + return nil, &resourceSdk.SyncOpResults{}, nil +} + +func (o *appResourceType) List( + ctx context.Context, + parentID *v2.ResourceId, + opts resourceSdk.SyncOpAttrs, +) ([]*v2.Resource, *resourceSdk.SyncOpResults, error) { + var annos annotations.Annotations + if parentID == nil { + return nil, &resourceSdk.SyncOpResults{}, nil + } + + bag, page, err := parsePageToken(opts.PageToken.Token, &v2.ResourceId{ResourceType: resourceTypeApp.Id}) + if err != nil { + return nil, nil, err + } + + orgName, err := o.orgCache.GetOrgName(ctx, opts.Session, parentID) + if err != nil { + return nil, nil, err + } + + installations, resp, err := o.client.Organizations.ListInstallations(ctx, orgName, &github.ListOptions{ + Page: page, + PerPage: opts.PageToken.Size, + }) + if err != nil { + // Listing org installations requires "Organization administration" + // read access. PAT auth is validated as an org admin, but a GitHub + // App installation token may not be granted that permission. Degrade + // gracefully so the rest of the sync still completes. + if resp != nil && (resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusNotFound) { + ctxzap.Extract(ctx).Warn("baton-github: cannot list org app installations; skipping app sync for org. "+ + "This requires Organization administration (read) access on the configured credentials.", + zap.String("org", orgName), + zap.Int("http_status", resp.StatusCode), + ) + return nil, &resourceSdk.SyncOpResults{}, nil + } + return nil, nil, wrapGitHubError(err, resp, "github-connector: failed to list organization app installations") + } + + restApiRateLimit, err := extractRateLimitData(resp) + if err != nil { + return nil, nil, err + } + annos.WithRateLimiting(restApiRateLimit) + + nextPage, _, err := parseResp(resp) + if err != nil { + return nil, nil, err + } + + pageToken, err := bag.NextToken(nextPage) + if err != nil { + return nil, nil, err + } + + var rv []*v2.Resource + for _, installation := range installations.Installations { + resource, err := appResource(ctx, installation, parentID) + if err != nil { + return nil, &resourceSdk.SyncOpResults{ + NextPageToken: pageToken, + Annotations: annos, + }, err + } + rv = append(rv, resource) + } + + return rv, &resourceSdk.SyncOpResults{ + NextPageToken: pageToken, + Annotations: annos, + }, nil +} + +func AppBuilder(client *github.Client, orgCache *orgNameCache) *appResourceType { + return &appResourceType{ + resourceType: resourceTypeApp, + client: client, + orgCache: orgCache, + } +} diff --git a/pkg/connector/app_test.go b/pkg/connector/app_test.go new file mode 100644 index 00000000..eb5e4e4c --- /dev/null +++ b/pkg/connector/app_test.go @@ -0,0 +1,47 @@ +package connector + +import ( + "context" + "testing" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + resourceSdk "github.com/conductorone/baton-sdk/pkg/types/resource" + "github.com/google/go-github/v69/github" + "github.com/stretchr/testify/require" +) + +func TestAppResource(t *testing.T) { + ctx := context.Background() + + installation := &github.Installation{ + ID: github.Ptr(int64(42)), + AppID: github.Ptr(int64(7)), + AppSlug: github.Ptr("octo-app"), + TargetType: github.Ptr("Organization"), + RepositorySelection: github.Ptr("all"), + HTMLURL: github.Ptr("https://github.com/organizations/acme/settings/installations/42"), + Account: &github.User{Login: github.Ptr("acme")}, + } + parent := &v2.ResourceId{ResourceType: resourceTypeOrg.Id, Resource: "1"} + + resource, err := appResource(ctx, installation, parent) + require.NoError(t, err) + require.Equal(t, "octo-app", resource.DisplayName) + require.Equal(t, "42", resource.Id.Resource) + require.Equal(t, resourceTypeApp.Id, resource.Id.ResourceType) + require.Equal(t, parent.Resource, resource.ParentResourceId.Resource) + + nhi, err := resourceSdk.GetNonHumanIdentityTrait(resource) + require.NoError(t, err) + require.Equal(t, v2.NonHumanIdentityTrait_NHI_TYPE_APP_REGISTRATION, nhi.GetNhiType()) + require.Equal(t, "github.app", nhi.GetNhiDetail()) + + app, err := resourceSdk.GetAppTrait(resource) + require.NoError(t, err) + appID, ok := resourceSdk.GetProfileInt64Value(app.GetProfile(), "app_id") + require.True(t, ok) + require.Equal(t, int64(7), appID) + login, ok := resourceSdk.GetProfileStringValue(app.GetProfile(), "account_login") + require.True(t, ok) + require.Equal(t, "acme", login) +} diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 7705b46c..024e1fc1 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -99,6 +99,12 @@ var ( Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_LICENSE_PROFILE}, Annotations: skipEntitlementsAnnotations("license"), } + resourceTypeApp = &v2.ResourceType{ + Id: "app", + DisplayName: "GitHub App", + Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_APP}, + Annotations: annotations.New(&v2.SkipEntitlementsAndGrants{}), + } ) type GitHub struct { @@ -127,6 +133,7 @@ func (gh *GitHub) ResourceSyncers(ctx context.Context) []connectorbuilder.Resour orgCache: gh.orgCache, orgs: gh.orgs, }), + AppBuilder(gh.client, gh.orgCache), } if gh.syncSecrets { diff --git a/pkg/connector/org.go b/pkg/connector/org.go index 5bf3bc43..4df7fa9a 100644 --- a/pkg/connector/org.go +++ b/pkg/connector/org.go @@ -55,6 +55,7 @@ func organizationResource( &v2.ChildResourceType{ResourceTypeId: resourceTypeRepository.Id}, &v2.ChildResourceType{ResourceTypeId: resourceTypeOrgRole.Id}, &v2.ChildResourceType{ResourceTypeId: resourceTypeInvitation.Id}, + &v2.ChildResourceType{ResourceTypeId: resourceTypeApp.Id}, } if syncSecrets { annotations = append(annotations, &v2.ChildResourceType{ResourceTypeId: resourceTypeApiToken.Id}) From e1332c369d8db1472132f54e333cd81e32149dec Mon Sep 17 00:00:00 2001 From: Paul Querna Date: Sun, 31 May 2026 19:41:49 +0000 Subject: [PATCH 2/5] =?UTF-8?q?ci:=20install=20baton=20CLI=20from=20baton-?= =?UTF-8?q?sdk@v0.11.0=20=E2=80=94=20conductorone/baton=20is=20archived=20?= =?UTF-8?q?(v0.4.5)=20and=20can't=20resolve=20NonHumanIdentityTrait?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: c1-squire-dev[bot] --- .github/workflows/ci.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 38f843ad..51e4a8db 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,6 +15,18 @@ jobs: go-version-file: go.mod - name: Build baton-github run: go build ./cmd/baton-github + - name: Install baton CLI from baton-sdk (conductorone/baton is archived) + run: | + set -euxo pipefail + BATON_VERSION=v0.11.0 + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + ARCH=$(uname -m) + if [ "${ARCH}" = "x86_64" ]; then ARCH="amd64"; fi + FILENAME="baton-${BATON_VERSION}-${OS}-${ARCH}.tar.gz" + curl -fsSL -O "https://github.com/conductorone/baton-sdk/releases/download/${BATON_VERSION}/${FILENAME}" + tar xzf "${FILENAME}" + mv baton /usr/local/bin/baton + baton --version - name: Grant/revoke uses: ConductorOne/github-workflows/actions/sync-test@v2 with: From c66bcb603ce95a7fc5d38a86b82e98c3aa334714 Mon Sep 17 00:00:00 2001 From: Luisina Santos Date: Wed, 3 Jun 2026 16:06:02 -0300 Subject: [PATCH 3/5] nhi: clean up GitHub Apps syncer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove V1Identifier from app resource (new resource type, no v1 history) - Remove manual 403/404 skip; capability permissions handle access gating - Add CapabilityPermissions annotation (organization_administration:read) to resourceTypeApp - Extract nextPageToken() helper combining parseResp + bag.NextToken; apply to app, api_token, org, team, repository builders — also fixes latent double-extractRateLimitData bug in those builders - Update README and connector.mdx: add GitHub Apps (NHI) to data model, capabilities table, and fine-grained token permissions Co-Authored-By: Claude Sonnet 4.6 --- README.md | 3 ++- docs/connector.mdx | 2 ++ pkg/connector/api_token.go | 15 +------------ pkg/connector/app.go | 44 ++++++------------------------------- pkg/connector/connector.go | 2 +- pkg/connector/helpers.go | 35 ++++++++++++++++++++++++++--- pkg/connector/org.go | 7 +----- pkg/connector/repository.go | 7 +----- pkg/connector/team.go | 7 +----- 9 files changed, 48 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 8db4c3e7..0c6cc073 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ baton resources - Users - Teams - Repositories +- GitHub Apps (installed in organizations, synced as non-human identities) By default, `baton-github` will sync information from any organizations that the provided credential has Administrator permissions on. You can specify exactly which organizations you would like to sync using the `--orgs` flag. @@ -105,7 +106,7 @@ Use "baton-github [command] --help" for more information about a command. To use this Baton connector, you need to create a GitHub organization access token with the following permissions: Org: -- Administration: Read-only (required to detect SAML/SSO configuration) +- Administration: Read-only (required to detect SAML/SSO configuration and to sync installed GitHub Apps) - Member Read and Write Repo: diff --git a/docs/connector.mdx b/docs/connector.mdx index ddcf66ca..2286203a 100644 --- a/docs/connector.mdx +++ b/docs/connector.mdx @@ -20,6 +20,7 @@ Use this integration if your organization accesses GitHub at `github.com`. If yo | Repositories | | | | Teams | | | | Orgs | | | +| GitHub Apps (NHI) | | | | Secrets - API keys | | | The GitHub connector supports [automatic account provisioning and deprovisioning](/product/admin/account-provisioning). New accounts will send an invitation to the account owner; if an invitation is pending, the account status will be shown as **Unspecified**. @@ -123,6 +124,7 @@ In the **Permissions** section of the page, give the token the following permiss - Organization permissions: + - **Administration**: Read-only access (required to sync installed GitHub Apps) - **Members**: Read and write access - **Custom organization roles**: Read and write access diff --git a/pkg/connector/api_token.go b/pkg/connector/api_token.go index a1e5b46a..b16b84bb 100644 --- a/pkg/connector/api_token.go +++ b/pkg/connector/api_token.go @@ -5,7 +5,6 @@ import ( "strconv" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" - "github.com/conductorone/baton-sdk/pkg/annotations" resourceSdk "github.com/conductorone/baton-sdk/pkg/types/resource" "github.com/google/go-github/v69/github" ) @@ -69,7 +68,6 @@ func (o *apiTokenResourceType) List( parentID *v2.ResourceId, opts resourceSdk.SyncOpAttrs, ) ([]*v2.Resource, *resourceSdk.SyncOpResults, error) { - var annotations annotations.Annotations if parentID == nil { return nil, &resourceSdk.SyncOpResults{}, nil } @@ -94,18 +92,7 @@ func (o *apiTokenResourceType) List( 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 - } - annotations.WithRateLimiting(restApiRateLimit) - - nextPage, _, err := parseResp(resp) - if err != nil { - return nil, nil, err - } - - pageToken, err := bag.NextToken(nextPage) + pageToken, annotations, err := nextPageToken(bag, resp) if err != nil { return nil, nil, err } diff --git a/pkg/connector/app.go b/pkg/connector/app.go index 2ebc182c..cafe9ed9 100644 --- a/pkg/connector/app.go +++ b/pkg/connector/app.go @@ -3,15 +3,10 @@ package connector import ( "context" "fmt" - "net/http" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" - "github.com/conductorone/baton-sdk/pkg/annotations" resourceSdk "github.com/conductorone/baton-sdk/pkg/types/resource" "github.com/google/go-github/v69/github" - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" ) // appResource builds a GitHub App resource from one organization installation. @@ -42,21 +37,20 @@ func appResource(ctx context.Context, installation *github.Installation, parentR profile["repository_selection"] = installation.GetRepositorySelection() } - annos := []proto.Message{ - &v2.V1Identifier{Id: fmt.Sprintf("app:%d", installation.GetID())}, + opts := []resourceSdk.ResourceOption{ + resourceSdk.WithParentResourceID(parentResourceID), + resourceSdk.WithAppTrait(resourceSdk.WithAppProfile(profile)), + resourceSdk.WithNHIType(v2.NonHumanIdentityTrait_NHI_TYPE_APP_REGISTRATION, "github.app"), } if installation.HTMLURL != nil { - annos = append(annos, &v2.ExternalLink{Url: installation.GetHTMLURL()}) + opts = append(opts, resourceSdk.WithAnnotation(&v2.ExternalLink{Url: installation.GetHTMLURL()})) } return resourceSdk.NewResource( displayName, resourceTypeApp, installation.GetID(), - resourceSdk.WithParentResourceID(parentResourceID), - resourceSdk.WithAppTrait(resourceSdk.WithAppProfile(profile)), - resourceSdk.WithNHIType(v2.NonHumanIdentityTrait_NHI_TYPE_APP_REGISTRATION, "github.app"), - resourceSdk.WithAnnotation(annos...), + opts..., ) } @@ -85,7 +79,6 @@ func (o *appResourceType) List( parentID *v2.ResourceId, opts resourceSdk.SyncOpAttrs, ) ([]*v2.Resource, *resourceSdk.SyncOpResults, error) { - var annos annotations.Annotations if parentID == nil { return nil, &resourceSdk.SyncOpResults{}, nil } @@ -105,33 +98,10 @@ func (o *appResourceType) List( PerPage: opts.PageToken.Size, }) if err != nil { - // Listing org installations requires "Organization administration" - // read access. PAT auth is validated as an org admin, but a GitHub - // App installation token may not be granted that permission. Degrade - // gracefully so the rest of the sync still completes. - if resp != nil && (resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusNotFound) { - ctxzap.Extract(ctx).Warn("baton-github: cannot list org app installations; skipping app sync for org. "+ - "This requires Organization administration (read) access on the configured credentials.", - zap.String("org", orgName), - zap.Int("http_status", resp.StatusCode), - ) - return nil, &resourceSdk.SyncOpResults{}, nil - } return nil, nil, wrapGitHubError(err, resp, "github-connector: failed to list organization app installations") } - restApiRateLimit, err := extractRateLimitData(resp) - if err != nil { - return nil, nil, err - } - annos.WithRateLimiting(restApiRateLimit) - - nextPage, _, err := parseResp(resp) - if err != nil { - return nil, nil, err - } - - pageToken, err := bag.NextToken(nextPage) + pageToken, annos, err := nextPageToken(bag, resp) if err != nil { return nil, nil, err } diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 024e1fc1..1987212f 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -103,7 +103,7 @@ var ( Id: "app", DisplayName: "GitHub App", Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_APP}, - Annotations: annotations.New(&v2.SkipEntitlementsAndGrants{}), + Annotations: skipEntitlementsAndGrantsAnnotations("app", "organization_administration:read"), } ) diff --git a/pkg/connector/helpers.go b/pkg/connector/helpers.go index ef37f6f5..ece84762 100644 --- a/pkg/connector/helpers.go +++ b/pkg/connector/helpers.go @@ -76,22 +76,37 @@ func newOrgNameCache(c *github.Client) *orgNameCache { } } -func v1AnnotationsForResourceType(resourceTypeID string) annotations.Annotations { +func v1AnnotationsForResourceType(resourceTypeID string, permissions ...string) annotations.Annotations { annos := annotations.Annotations{} annos.Update(&v2.V1Identifier{ Id: resourceTypeID, }) + if len(permissions) > 0 { + caps := make([]*v2.CapabilityPermission, 0, len(permissions)) + for _, p := range permissions { + caps = append(caps, &v2.CapabilityPermission{Permission: p}) + } + annos.Update(&v2.CapabilityPermissions{Permissions: caps}) + } + return annos } -func skipEntitlementsAnnotations(resourceTypeID string) annotations.Annotations { - annos := v1AnnotationsForResourceType(resourceTypeID) +func skipEntitlementsAnnotations(resourceTypeID string, permissions ...string) annotations.Annotations { + annos := v1AnnotationsForResourceType(resourceTypeID, permissions...) annos.Update(&v2.SkipEntitlements{}) return annos } +func skipEntitlementsAndGrantsAnnotations(resourceTypeID string, permissions ...string) annotations.Annotations { + annos := v1AnnotationsForResourceType(resourceTypeID, permissions...) + annos.Update(&v2.SkipEntitlementsAndGrants{}) + + return annos +} + // parseResourceToGitHub returns the upstream API ID by looking at the last 'part' of the resource ID. func parseResourceToGitHub(id *v2.ResourceId) (int64, error) { idParts := strings.Split(id.Resource, ":") @@ -129,6 +144,20 @@ func convertPageToken(token string) (int, error) { return strconv.Atoi(token) } +// nextPageToken combines parseResp and bag.NextToken into a single call, +// returning the serialized page token and rate-limit annotations together. +func nextPageToken(bag *pagination.Bag, resp *github.Response) (string, annotations.Annotations, error) { + nextPage, annos, err := parseResp(resp) + if err != nil { + return "", nil, err + } + pageToken, err := bag.NextToken(nextPage) + if err != nil { + return "", nil, err + } + return pageToken, annos, nil +} + // fmtGitHubPageToken return a formatted string for a github page token. func fmtGitHubPageToken(pageToken int) string { if pageToken == 0 { diff --git a/pkg/connector/org.go b/pkg/connector/org.go index 4df7fa9a..4d17748d 100644 --- a/pkg/connector/org.go +++ b/pkg/connector/org.go @@ -109,12 +109,7 @@ func (o *orgResourceType) List( return nil, nil, wrapGitHubError(err, resp, "github-connector: failed to fetch organizations") } - nextPage, reqAnnos, err := parseResp(resp) - if err != nil { - return nil, nil, err - } - - pageToken, err := bag.NextToken(nextPage) + pageToken, reqAnnos, err := nextPageToken(bag, resp) if err != nil { return nil, nil, err } diff --git a/pkg/connector/repository.go b/pkg/connector/repository.go index f9eb3b10..d5a6e961 100644 --- a/pkg/connector/repository.go +++ b/pkg/connector/repository.go @@ -97,12 +97,7 @@ func (o *repositoryResourceType) List(ctx context.Context, parentID *v2.Resource return nil, nil, wrapGitHubError(err, resp, "github-connector: failed to list repositories") } - nextPage, reqAnnos, err := parseResp(resp) - if err != nil { - return nil, nil, err - } - - pageToken, err := bag.NextToken(nextPage) + pageToken, reqAnnos, err := nextPageToken(bag, resp) if err != nil { return nil, nil, err } diff --git a/pkg/connector/team.go b/pkg/connector/team.go index 6cd75739..8e1579ba 100644 --- a/pkg/connector/team.go +++ b/pkg/connector/team.go @@ -102,7 +102,7 @@ func (o *teamResourceType) List(ctx context.Context, parentID *v2.ResourceId, op return nil, nil, wrapGitHubError(err, resp, "github-connector: failed to list teams") } - nextPage, reqAnnos, err := parseResp(resp) + pageToken, reqAnnos, err := nextPageToken(bag, resp) if err != nil { return nil, nil, err } @@ -125,11 +125,6 @@ func (o *teamResourceType) List(ctx context.Context, parentID *v2.ResourceId, op rv = append(rv, tr) } - pageToken, err := bag.NextToken(nextPage) - if err != nil { - return nil, nil, err - } - return rv, &rType.SyncOpResults{ NextPageToken: pageToken, Annotations: reqAnnos, From 79ee5fd450743678a00fc7e027b9de2d93afb776 Mon Sep 17 00:00:00 2001 From: Luisina Santos Date: Wed, 3 Jun 2026 16:06:43 -0300 Subject: [PATCH 4/5] ci: install baton CLI from baton-sdk@v0.11.0 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yaml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 51e4a8db..91156a07 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,20 +15,8 @@ jobs: go-version-file: go.mod - name: Build baton-github run: go build ./cmd/baton-github - - name: Install baton CLI from baton-sdk (conductorone/baton is archived) - run: | - set -euxo pipefail - BATON_VERSION=v0.11.0 - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - ARCH=$(uname -m) - if [ "${ARCH}" = "x86_64" ]; then ARCH="amd64"; fi - FILENAME="baton-${BATON_VERSION}-${OS}-${ARCH}.tar.gz" - curl -fsSL -O "https://github.com/conductorone/baton-sdk/releases/download/${BATON_VERSION}/${FILENAME}" - tar xzf "${FILENAME}" - mv baton /usr/local/bin/baton - baton --version - name: Grant/revoke - uses: ConductorOne/github-workflows/actions/sync-test@v2 + uses: ConductorOne/github-workflows/actions/sync-test@v4 with: connector: ./baton-github baton-entitlement: 'repository:642588514:admin' From ea15782b8238f4fb24c706e8d8a7c2375b141bed Mon Sep 17 00:00:00 2001 From: Luisina Santos Date: Wed, 3 Jun 2026 16:37:11 -0300 Subject: [PATCH 5/5] fix ci --- .github/workflows/ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 91156a07..adf7df04 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest env: BATON_TOKEN: ${{ secrets.BATON_TOKEN }} - BATON_ORGS: ConductorOne + BATON_ORGS: ${{ vars.BATON_ORGS }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -19,6 +19,6 @@ jobs: uses: ConductorOne/github-workflows/actions/sync-test@v4 with: connector: ./baton-github - baton-entitlement: 'repository:642588514:admin' - baton-principal: '166871869' - baton-principal-type: 'user' + baton-entitlement: ${{ vars.BATON_ENTITLEMENT_REPOSITORY }} + baton-principal: ${{ vars.BATON_PRINCIPAL }} + baton-principal-type: ${{ vars.BATON_PRINCIPAL_TYPE }}